├── .gitignore ├── doc ├── hub75.png ├── hub75.json ├── overview.md └── framebuffer.md ├── Makefile ├── sw ├── mkgamma.py └── hub75_timing.py ├── no2core.mk ├── rtl ├── hub75_gamma.v ├── hub75_linebuffer.v ├── hub75_blanking.v ├── hub75_shift.v ├── hub75_colormap.v ├── hub75_scan.v ├── hub75_phy.v ├── hub75_init_inject.v ├── hub75_fb_writein.v ├── hub75_bcm.v ├── hub75_phy_ddr.v ├── hub75_fb_readout.v ├── hub75_framebuffer.v └── hub75_top.v ├── README.md ├── sim └── hub75_init_inject_tb.v └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | build-tmp 2 | __pycache__ 3 | *.vcd 4 | .*.swp 5 | -------------------------------------------------------------------------------- /doc/hub75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/no2fpga/no2hub75/HEAD/doc/hub75.png -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CORE := hub75 2 | 3 | NO2BUILD_DIR ?= $(abspath ../../build) 4 | include $(NO2BUILD_DIR)/core-rules.mk 5 | -------------------------------------------------------------------------------- /doc/hub75.json: -------------------------------------------------------------------------------- 1 | {signal: [ 2 | {name: 'addr', wave: '4..........5........................3.', data: ['N-1', 'N', 'N+1'] }, 3 | {name: 'blank', wave: '10.......1..0.1....0...1..0.......1..0' }, 4 | {name: 'le', wave: '0.........10.....10.....10.........10.' }, 5 | {name: 'clk', wave: 'l.p...l......p...l..p...l..p...l......p', phase: 0.5 }, 6 | {name: 'data', wave: 'x5555x......5555x..5555x..3333x......3' }, 7 | ]} 8 | -------------------------------------------------------------------------------- /sw/mkgamma.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Computes gamma LUT 4 | # 5 | # Copyright (C) 2019-2020 Sylvain Munaut 6 | # SPDX-License-Identifier: MIT 7 | # 8 | 9 | import math 10 | 11 | GAMMA = 2.0 12 | WIDTH_IN = 8 13 | WIDTH_OUT = 16 14 | 15 | 16 | for iv in range(1 << WIDTH_IN): 17 | ov = 1.0 * iv / ((1 << WIDTH_IN) - 1) 18 | ov = math.pow(ov, GAMMA) 19 | ov = ov * ((1 << WIDTH_OUT) - 1) 20 | ov = round(ov) 21 | print("%04x" % ov) 22 | -------------------------------------------------------------------------------- /no2core.mk: -------------------------------------------------------------------------------- 1 | CORE := no2hub75 2 | 3 | DEPS_no2hub75 := no2misc 4 | 5 | RTL_SRCS_no2hub75 := $(addprefix rtl/, \ 6 | hub75_bcm.v \ 7 | hub75_blanking.v \ 8 | hub75_colormap.v \ 9 | hub75_fb_readout.v \ 10 | hub75_fb_writein.v \ 11 | hub75_framebuffer.v \ 12 | hub75_gamma.v \ 13 | hub75_init_inject.v \ 14 | hub75_linebuffer.v \ 15 | hub75_phy.v \ 16 | hub75_phy_ddr.v \ 17 | hub75_scan.v \ 18 | hub75_shift.v \ 19 | hub75_top.v \ 20 | ) 21 | 22 | TESTBENCHES_no2hub75 := \ 23 | hub75_init_inject_tb \ 24 | 25 | PREREQ_no2hub75 := \ 26 | $(BUILD_TMP)/gamma_table.hex 27 | 28 | include $(NO2BUILD_DIR)/core-magic.mk 29 | 30 | $(BUILD_TMP)/gamma_table.hex: $(CORE_no2hub75_DIR)/sw/mkgamma.py 31 | $(CORE_no2hub75_DIR)/sw/mkgamma.py > $@ 32 | -------------------------------------------------------------------------------- /rtl/hub75_gamma.v: -------------------------------------------------------------------------------- 1 | /* 2 | * hub75_gamma.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2019-2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_gamma #( 13 | parameter IW = 8, 14 | parameter OW = 10 15 | )( 16 | input wire [IW-1:0] in, 17 | output wire [OW-1:0] out, 18 | input wire enable, 19 | input wire clk 20 | ); 21 | reg [15:0] gamma_rom [0:255]; 22 | wire [ 7:0] rd_addr; 23 | reg [15:0] rd_data; 24 | 25 | initial 26 | $readmemh("gamma_table.hex", gamma_rom); 27 | 28 | always @(posedge clk) 29 | begin 30 | // Read 31 | if (enable) 32 | rd_data <= gamma_rom[rd_addr]; 33 | end 34 | 35 | genvar i; 36 | generate 37 | for (i=0; i<8; i=i+1) 38 | assign rd_addr[7-i] = in[IW-1-(i%IW)]; 39 | endgenerate 40 | 41 | assign out = rd_data[15:16-OW]; 42 | 43 | endmodule // hub75_gamma 44 | -------------------------------------------------------------------------------- /rtl/hub75_linebuffer.v: -------------------------------------------------------------------------------- 1 | /* 2 | * hub75_linebuffer.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2019-2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_linebuffer #( 13 | parameter N_WORDS = 1, 14 | parameter WORD_WIDTH = 24, 15 | parameter ADDR_WIDTH = 6 16 | )( 17 | input wire [ADDR_WIDTH-1:0] wr_addr, 18 | input wire [(N_WORDS*WORD_WIDTH)-1:0] wr_data, 19 | input wire [N_WORDS-1:0] wr_mask, 20 | input wire wr_ena, 21 | 22 | input wire [ADDR_WIDTH-1:0] rd_addr, 23 | output reg [(N_WORDS*WORD_WIDTH)-1:0] rd_data, 24 | input wire rd_ena, 25 | 26 | input wire clk 27 | ); 28 | integer i; 29 | 30 | (* no_rw_check *) 31 | reg [(N_WORDS*WORD_WIDTH)-1:0] ram [(1< 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_blanking #( 13 | parameter integer N_PLANES = 8 14 | )( 15 | // PHY 16 | output wire phy_blank, 17 | 18 | // Control 19 | input wire [N_PLANES-1:0] ctrl_plane, 20 | input wire ctrl_go, 21 | output wire ctrl_rdy, 22 | 23 | // Config 24 | input wire [7:0] cfg_bcm_bit_len, 25 | 26 | // Clock / Reset 27 | input wire clk, 28 | input wire rst 29 | ); 30 | 31 | // Signals 32 | // ------- 33 | 34 | wire active; 35 | wire plane_cnt_ce; 36 | reg [N_PLANES:0] plane_cnt; 37 | reg [7:0] bit_cnt; 38 | wire bit_cnt_trig; 39 | 40 | 41 | // Control 42 | // ------- 43 | 44 | // Active 45 | assign active = plane_cnt[N_PLANES]; 46 | 47 | // Plane length counter 48 | always @(posedge clk or posedge rst) 49 | if (rst) 50 | plane_cnt <= 0; 51 | else if (plane_cnt_ce) 52 | plane_cnt <= (ctrl_go ? { 1'b1, ctrl_plane } : plane_cnt) - 1; 53 | 54 | assign plane_cnt_ce = (bit_cnt_trig & active) | ctrl_go; 55 | 56 | // Base len bit counter 57 | always @(posedge clk) 58 | if (~active | bit_cnt_trig) 59 | bit_cnt <= cfg_bcm_bit_len; 60 | else 61 | bit_cnt <= bit_cnt - 1; 62 | 63 | assign bit_cnt_trig = bit_cnt[7]; 64 | 65 | // Ready 66 | assign ctrl_rdy = ~active; 67 | 68 | 69 | // PHY 70 | // --- 71 | 72 | assign phy_blank = ~active; 73 | 74 | endmodule // hub75_blanking 75 | -------------------------------------------------------------------------------- /sim/hub75_init_inject_tb.v: -------------------------------------------------------------------------------- 1 | /* 2 | * hub75_init_inject_tb.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2019-2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | `timescale 1ns / 100ps 12 | 13 | module hub75_init_inject_tb; 14 | 15 | // Signals 16 | reg rst = 1; 17 | reg clk = 1; 18 | 19 | wire phy_out_addr_inc; 20 | wire phy_out_addr_rst; 21 | wire [4:0] phy_out_addr; 22 | wire [5:0] phy_out_data; 23 | wire phy_out_clk; 24 | wire phy_out_le; 25 | wire phy_out_blank; 26 | 27 | wire scan_go_in; 28 | wire scan_go_out; 29 | wire scan_rdy_in; 30 | wire scan_rdy_out; 31 | wire bcm_rdy_in; 32 | 33 | // Setup recording 34 | initial begin 35 | $dumpfile("hub75_init_inject_tb.vcd"); 36 | $dumpvars(0,hub75_init_inject_tb); 37 | end 38 | 39 | // Reset pulse 40 | initial begin 41 | # 31 rst = 0; 42 | # 20000 $finish; 43 | end 44 | 45 | // Clocks 46 | always #5 clk = !clk; 47 | 48 | // DUT 49 | hub75_init_inject dut_I ( 50 | .phy_in_addr_inc(1'b0), 51 | .phy_in_addr_rst(1'b0), 52 | .phy_in_addr(5'h00), 53 | .phy_in_data(6'h00), 54 | .phy_in_clk(1'b0), 55 | .phy_in_le(1'b0), 56 | .phy_in_blank(1'b1), 57 | .phy_out_addr_inc(phy_out_addr_inc), 58 | .phy_out_addr_rst(phy_out_addr_rst), 59 | .phy_out_addr(phy_out_addr), 60 | .phy_out_data(phy_out_data), 61 | .phy_out_clk(phy_out_clk), 62 | .phy_out_le(phy_out_le), 63 | .phy_out_blank(phy_out_blank), 64 | .scan_go_in(scan_go_in), 65 | .scan_go_out(scan_go_out), 66 | .scan_rdy_in(scan_rdy_in), 67 | .scan_rdy_out(scan_rdy_out), 68 | .bcm_rdy_in(bcm_rdy_in), 69 | .clk(clk), 70 | .rst(rst) 71 | ); 72 | 73 | // Dummy 74 | assign scan_go_in = scan_rdy_out; 75 | assign scan_rdy_in = 1'b1; 76 | assign bcm_rdy_in = 1'b1; 77 | 78 | // PHY 79 | hub75_phy phy_I ( 80 | .phy_addr_inc(phy_out_addr_inc), 81 | .phy_addr_rst(phy_out_addr_rst), 82 | .phy_addr(phy_out_addr), 83 | .phy_data(phy_out_data), 84 | .phy_clk(phy_out_clk), 85 | .phy_le(phy_out_le), 86 | .phy_blank(phy_out_blank), 87 | .clk(clk), 88 | .rst(rst) 89 | ); 90 | 91 | endmodule 92 | -------------------------------------------------------------------------------- /rtl/hub75_shift.v: -------------------------------------------------------------------------------- 1 | /* 2 | * hub75_shift.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2019-2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_shift #( 13 | parameter integer N_BANKS = 2, 14 | parameter integer N_COLS = 64, 15 | parameter integer N_CHANS = 3, 16 | parameter integer N_PLANES = 8, 17 | 18 | // Auto-set 19 | parameter integer SDW = N_BANKS * N_CHANS, 20 | parameter integer LOG_N_COLS = $clog2(N_COLS) 21 | )( 22 | // PHY 23 | output wire [SDW-1:0] phy_data, 24 | output wire phy_clk, 25 | 26 | // RAM interface 27 | input wire [(N_BANKS*N_CHANS*N_PLANES)-1:0] ram_data, 28 | output wire [LOG_N_COLS-1:0] ram_col_addr, 29 | output wire ram_rden, 30 | 31 | // Control 32 | input wire [N_PLANES-1:0] ctrl_plane, 33 | input wire ctrl_go, 34 | output wire ctrl_rdy, 35 | 36 | // Clock / Reset 37 | input wire clk, 38 | input wire rst 39 | ); 40 | 41 | genvar i; 42 | 43 | // Signals 44 | // ------- 45 | 46 | reg active_0; 47 | reg active_1; 48 | reg active_2; 49 | reg [LOG_N_COLS:0] cnt_0; 50 | reg cnt_last_0; 51 | 52 | wire [SDW-1:0] ram_data_bit; 53 | reg [SDW-1:0] data_2; 54 | 55 | 56 | // Control logic 57 | // ------------- 58 | 59 | // Active / Valid flag 60 | always @(posedge clk or posedge rst) 61 | if (rst) begin 62 | active_0 <= 1'b0; 63 | active_1 <= 1'b0; 64 | active_2 <= 1'b0; 65 | end else begin 66 | active_0 <= (active_0 & ~cnt_last_0) | ctrl_go; 67 | active_1 <= active_0; 68 | active_2 <= active_1; 69 | end 70 | 71 | // Counter 72 | always @(posedge clk) 73 | if (ctrl_go) begin 74 | cnt_0 <= 0; 75 | cnt_last_0 <= 1'b0; 76 | end else if (active_0) begin 77 | cnt_0 <= cnt_0 + 1; 78 | cnt_last_0 <= (cnt_0 == (N_COLS - 2)); 79 | end 80 | 81 | // Ready ? 82 | assign ctrl_rdy = ~active_0; 83 | 84 | 85 | // Data path 86 | // --------- 87 | 88 | // RAM access 89 | assign ram_rden = active_0; 90 | assign ram_col_addr = cnt_0[LOG_N_COLS-1:0]; 91 | 92 | // Data plane mux 93 | generate 94 | for (i=0; i 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_colormap #( 13 | parameter integer N_CHANS = 3, 14 | parameter integer N_PLANES = 8, 15 | parameter integer BITDEPTH = 24, 16 | parameter integer USER_WIDTH = 1 17 | )( 18 | // Input pixel 19 | input wire [BITDEPTH-1:0] in_data, 20 | input wire [USER_WIDTH-1:0] in_user, 21 | input wire in_valid, 22 | output reg in_ready, 23 | 24 | // Output pixel 25 | output wire [(N_CHANS*N_PLANES)-1:0] out_data, 26 | output wire [USER_WIDTH-1:0] out_user, 27 | output reg out_valid, 28 | 29 | // Clock / Reset 30 | input wire clk, 31 | input wire rst 32 | ); 33 | // Signals 34 | // ------- 35 | 36 | wire [7:0] c0; 37 | wire [7:0] c1; 38 | wire [7:0] c2; 39 | reg [7:0] cmux; 40 | 41 | reg [1:0] cnt; 42 | 43 | wire [N_PLANES-1:0] do; 44 | reg [N_PLANES-1:0] do_r[0:1]; 45 | 46 | 47 | // Control 48 | // ------- 49 | 50 | // Little note: Technically we need only 3 cycles to lookup the 51 | // 3 colors. But to avoid having some registers to pipeline the 'user' 52 | // data, we take 4 cycles. It doesn't matter anyway since we'll have 53 | // to wait to pipe the data to the LCD anyway, so 'wasting' a cycle 54 | // here has no consequence. 55 | 56 | // Cycle counter 57 | always @(posedge clk) 58 | cnt <= in_valid ? (cnt + 1) : 2'b00; 59 | 60 | 61 | // Input stage 62 | // ----------- 63 | 64 | // Map color channels 65 | generate 66 | if (BITDEPTH == 24) begin 67 | assign c2 = in_data[23:16]; 68 | assign c1 = in_data[15: 8]; 69 | assign c0 = in_data[ 7: 0]; 70 | end else if (BITDEPTH == 16) begin 71 | assign c2 = { in_data[15:11], in_data[15:13] }; 72 | assign c1 = { in_data[10: 5], in_data[10: 9] }; 73 | assign c0 = { in_data[ 4: 0], in_data[ 4: 2] }; 74 | end else if (BITDEPTH == 8) begin 75 | assign c2 = { {2{in_data[7:5]}}, in_data[7:6] }; 76 | assign c1 = { {2{in_data[4:2]}}, in_data[4:3] }; 77 | assign c0 = { {4{in_data[1:0]}} }; 78 | end 79 | endgenerate 80 | 81 | // Mux 82 | always @(*) 83 | case (cnt) 84 | 2'b00: cmux = c0; 85 | 2'b01: cmux = c1; 86 | 2'b10: cmux = c2; 87 | default: cmux = 8'hx; 88 | endcase 89 | 90 | // When are we ready 91 | always @(posedge clk) 92 | in_ready <= (cnt == 2'b10); 93 | 94 | 95 | // Gamma LUT 96 | // --------- 97 | 98 | hub75_gamma #( 99 | .IW(8), 100 | .OW(N_PLANES) 101 | ) gamma_lut_I ( 102 | .in(cmux), 103 | .out(do), 104 | .enable(1'b1), 105 | .clk(clk) 106 | ); 107 | 108 | 109 | // Output stage 110 | // ------------ 111 | 112 | // Data 113 | always @(posedge clk) 114 | begin 115 | do_r[1] <= do; 116 | do_r[0] <= do_r[1]; 117 | end 118 | 119 | assign out_data = { do, do_r[1], do_r[0] }; 120 | 121 | // User infos 122 | assign out_user = in_user; 123 | 124 | // Valid signal 125 | always @(posedge clk) 126 | out_valid <= (cnt == 2'b10); 127 | 128 | endmodule // hub75_colormap 129 | -------------------------------------------------------------------------------- /rtl/hub75_scan.v: -------------------------------------------------------------------------------- 1 | /* 2 | * hub75_scan.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2019-2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_scan #( 13 | parameter integer N_ROWS = 32, 14 | 15 | parameter SCAN_MODE = "ZIGZAG", // 'LINEAR' or 'ZIGZAG' 16 | 17 | // Auto-set 18 | parameter integer LOG_N_ROWS = $clog2(N_ROWS) 19 | )( 20 | // BCM interface 21 | output wire [LOG_N_ROWS-1:0] bcm_row, 22 | output wire bcm_row_first, 23 | output wire bcm_go, 24 | input wire bcm_rdy, 25 | 26 | // Frame buffer read interface 27 | output wire [LOG_N_ROWS-1:0] fb_row_addr, 28 | output wire fb_row_load, // Back-buffer load request 29 | input wire fb_row_rdy, // Back-buffer loaded 30 | output wire fb_row_swap, // Buffer swap 31 | 32 | // Control 33 | input wire ctrl_go, 34 | output wire ctrl_rdy, 35 | 36 | // Clock / Reset 37 | input wire clk, 38 | input wire rst 39 | ); 40 | 41 | // Signals 42 | // ------- 43 | 44 | // FSM 45 | localparam 46 | ST_IDLE = 0, // Idle 47 | ST_LOAD = 1, // Load back-buffer with next-row 48 | ST_WAIT = 2, // Wait for back-buffer & BCM to be ready 49 | ST_PAINT = 3; // Swap buffer, issue BCM paint, go to next row 50 | 51 | reg [1:0] fsm_state; 52 | reg [1:0] fsm_state_next; 53 | 54 | // Row counter 55 | reg [LOG_N_ROWS-1:0] row; 56 | reg row_first; 57 | reg row_last; 58 | 59 | 60 | // FSM 61 | // --- 62 | 63 | // State register 64 | always @(posedge clk or posedge rst) 65 | if (rst) 66 | fsm_state <= ST_IDLE; 67 | else 68 | fsm_state <= fsm_state_next; 69 | 70 | // Next-State logic 71 | always @(*) 72 | begin 73 | // Default is to not move 74 | fsm_state_next = fsm_state; 75 | 76 | // Transitions ? 77 | case (fsm_state) 78 | ST_IDLE: 79 | if (ctrl_go) 80 | fsm_state_next = ST_LOAD; 81 | 82 | ST_LOAD: 83 | fsm_state_next = ST_WAIT; 84 | 85 | ST_WAIT: 86 | if (bcm_rdy & fb_row_rdy) 87 | fsm_state_next = ST_PAINT; 88 | 89 | ST_PAINT: 90 | fsm_state_next = row_last ? ST_IDLE : ST_LOAD; 91 | endcase 92 | end 93 | 94 | 95 | // Row counter 96 | // ----------- 97 | 98 | always @(posedge clk) 99 | if (fsm_state == ST_IDLE) begin 100 | row <= 0; 101 | row_first <= 1'b1; 102 | row_last <= 1'b0; 103 | end else if (fsm_state == ST_PAINT) begin 104 | if (SCAN_MODE == "ZIGZAG") begin 105 | row <= ~(row + {LOG_N_ROWS{row[LOG_N_ROWS-1]}}); 106 | row_first <= 1'b0; 107 | row_last <= (row == {1'b0, {(LOG_N_ROWS-1){1'b1}}}); 108 | end else begin 109 | row <= row + 1; 110 | row_first <= 1'b0; 111 | row_last <= (row == {{(LOG_N_ROWS-1){1'b1}}, 1'b0}); 112 | end 113 | end 114 | 115 | 116 | // External interfaces 117 | // ------------------- 118 | 119 | // BCM 120 | assign bcm_row = row; 121 | assign bcm_row_first = row_first; 122 | assign bcm_go = (fsm_state == ST_PAINT); 123 | 124 | // Frame Buffer pre loader 125 | assign fb_row_addr = row; 126 | assign fb_row_load = (fsm_state == ST_LOAD); 127 | assign fb_row_swap = (fsm_state == ST_PAINT); 128 | 129 | // Ready signal 130 | assign ctrl_rdy = (fsm_state == ST_IDLE); 131 | 132 | endmodule // hub75_scan 133 | -------------------------------------------------------------------------------- /rtl/hub75_phy.v: -------------------------------------------------------------------------------- 1 | /* 2 | * hub75_phy.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2019-2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_phy #( 13 | parameter integer N_BANKS = 2, 14 | parameter integer N_ROWS = 32, 15 | parameter integer N_CHANS = 3, 16 | parameter integer PHY_N = 1, // # of PHY in // 17 | parameter integer PHY_AIR = 0, // PHY Address Inc/Reset 18 | 19 | // Auto-set 20 | parameter integer SDW = N_BANKS * N_CHANS, 21 | parameter integer LOG_N_ROWS = $clog2(N_ROWS) 22 | )( 23 | // Hub75 interface pads 24 | output wire [PHY_N-1:0] hub75_addr_inc, 25 | output wire [PHY_N-1:0] hub75_addr_rst, 26 | output wire [(PHY_N*LOG_N_ROWS)-1:0] hub75_addr, 27 | output wire [SDW-1 :0] hub75_data, 28 | output wire [PHY_N-1:0] hub75_clk, 29 | output wire [PHY_N-1:0] hub75_le, 30 | output wire [PHY_N-1:0] hub75_blank, 31 | 32 | // PHY interface signals 33 | input wire phy_addr_inc, 34 | input wire phy_addr_rst, 35 | input wire [LOG_N_ROWS-1:0] phy_addr, 36 | input wire [SDW-1:0] phy_data, 37 | input wire phy_clk, 38 | input wire phy_le, 39 | input wire phy_blank, 40 | 41 | // Clock / Reset 42 | input wire clk, 43 | input wire rst 44 | ); 45 | // Signals 46 | reg phy_clk_f; 47 | 48 | // Address 49 | genvar i; 50 | generate 51 | if (PHY_AIR == 0) begin 52 | for (i=0; i | Process | ----> | Buffer | ----> | Process | ----> Driver 17 | '---------' '--------' '---------' 18 | ``` 19 | 20 | 21 | 22 | Write Process 23 | ------------- 24 | 25 | The write process top level is the `hub75_fb_writein` module. 26 | 27 | ``` 28 | .---------------, 29 | From | Double | To 30 | Pixel ---> | Row | ---> Frame 31 | Source | Buffer | Buffer 32 | '---------------' 33 | | 34 | ,---------------, 35 | <--> | Control logic | <--> 36 | '---------------' 37 | ``` 38 | 39 | This module contains a double row buffer. 40 | 41 | So the general usage is that the user side interface is free to write an 42 | entire row of pixels inside one of the row buffer (in any order and at 43 | any rate). 44 | 45 | When done, it can send a store command that will swap the double buffers, 46 | give it a new row buffer to write the next row, while in the background the 47 | first buffer is being written to the shared frame buffer. 48 | 49 | 50 | Frame Buffer 51 | ------------ 52 | 53 | The frame buffer storage element itself is based on the iCE40 SPRAMs blocks. 54 | Those have some limitations and thus the frame buffer logic has to contain 55 | logic to make the requires adaptations: 56 | 57 | * They are single-port and so there needs to be arbitration logic between 58 | the read and write side to avoid conflicts. 59 | 60 | * Each RAM is a fixed 16 bits and so width adaptation depending on the 61 | bits / pixels might be needed. 62 | 63 | * Depending on the required size for the frame buffer, the logic will 64 | automatically decide how many SPRAMs to use and how to combine them 65 | (in width or depth). The resulting geometry is displayed in the debug 66 | output during synthesis / simulation. 67 | 68 | All of this is handled internally in the `hub75_framebuffer` module. 69 | 70 | 71 | 72 | Read Process 73 | ------------ 74 | 75 | The read process top level is the `hub75_fb_readout`. 76 | 77 | ``` 78 | ,--------------------------------------, 79 | | Color Mapping | .--------, 80 | From | ,-----------, ,--------, | | Double | To 81 | Frame ---> | --> | Bit | --> | Gamma | | ---> | Row | ---> Panel 82 | Buffer | | Expansion | | Lookup | --> | | Buffer | 83 | | '-----------' '--------' | '--------' 84 | '----------|----------------|----------' | 85 | | | | 86 | ,------------------------------------------------------, 87 | <--> | Control logic | <--> 88 | '------------------------------------------------------' 89 | ``` 90 | 91 | This module also contains a double row buffer like the write process and the 92 | flow is very similar but in reverse. 93 | 94 | The 'user side' (which in this case is the core of the LED driver to pilot the 95 | LEDs) can request a row to be loaded from the shared frame buffer memory in 96 | the background. And when it's loaded, it can 'swap' the front/back buffer to 97 | actually read the data and it can also issue the command to load the next row. 98 | 99 | Another specificity is that this buffer loads the same row for all banks at 100 | a time since they will need to be send to the panels in parallel and so it 101 | actually buffers `N_BANKS` rows and not just one. 102 | 103 | Finally this is also during the read out that the final color mapping is done. 104 | This color mapping step is what converts from the `BITDEPTH` bits that have 105 | been provided to the user to the actual value that will be used for the BCM 106 | modulation of the RGB leds. 107 | 108 | This step can be modified to the user to suit their need (for instance, doing 109 | a palette lookup), but by default it does a bit expansion to 24 bits RGB 110 | (assuming a RGB332 or RGB565 or RGB888 depending on `BITDEPTH`) and then 111 | performs a gamma correction using a built-in LUT. 112 | -------------------------------------------------------------------------------- /rtl/hub75_init_inject.v: -------------------------------------------------------------------------------- 1 | /* 2 | * hub75_init_inject.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2019-2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_init_inject #( 13 | parameter integer N_BANKS = 2, 14 | parameter integer N_ROWS = 32, 15 | parameter integer N_COLS = 64, 16 | parameter integer N_CHANS = 3, 17 | 18 | parameter INIT_R1 = 16'h7FFF, 19 | parameter INIT_R2 = 16'h0040, 20 | 21 | // Auto-set 22 | parameter integer SDW = N_BANKS * N_CHANS, 23 | parameter integer LOG_N_ROWS = $clog2(N_ROWS), 24 | parameter integer LOG_N_COLS = $clog2(N_COLS) 25 | )( 26 | // PHY interface signals input 27 | input wire phy_in_addr_inc, 28 | input wire phy_in_addr_rst, 29 | input wire [LOG_N_ROWS-1:0] phy_in_addr, 30 | input wire [SDW-1:0] phy_in_data, 31 | input wire phy_in_clk, 32 | input wire phy_in_le, 33 | input wire phy_in_blank, 34 | 35 | // PHY interface signals input 36 | output reg phy_out_addr_inc, 37 | output reg phy_out_addr_rst, 38 | output reg [LOG_N_ROWS-1:0] phy_out_addr, 39 | output reg [SDW-1:0] phy_out_data, 40 | output reg phy_out_clk, 41 | output reg phy_out_le, 42 | output reg phy_out_blank, 43 | 44 | // Control 45 | input wire init_req, 46 | 47 | input wire scan_go_in, 48 | 49 | input wire bcm_rdy_in, 50 | output wire bcm_rdy_out, 51 | 52 | input wire shift_rdy_in, 53 | input wire blank_rdy_in, 54 | 55 | // Clock / Reset 56 | input wire clk, 57 | input wire rst 58 | ); 59 | 60 | // Signals 61 | // ------- 62 | 63 | // FSM 64 | localparam 65 | ST_IDLE = 0, // Idle 66 | ST_WAIT = 1, // Wait for all activity to be done so we can use the PHY 67 | ST_SHIFT = 2, // Shift the init 68 | ST_GO = 3; // Issue go to scan block 69 | 70 | reg [1:0] fsm_state; 71 | reg [1:0] fsm_state_next; 72 | 73 | // Request 74 | reg init_done; 75 | 76 | // Injection 77 | wire active; 78 | wire inject_data; 79 | wire inject_le; 80 | 81 | // Shift logic 82 | reg [LOG_N_COLS:0] col_cnt; 83 | reg col_last; 84 | reg col_le; 85 | wire col_rst; 86 | 87 | reg reg_sel; 88 | (* keep="true" *) wire [1:0] reg_bit; 89 | 90 | 91 | // FSM 92 | // --- 93 | 94 | // State register 95 | always @(posedge clk or posedge rst) 96 | if (rst) 97 | fsm_state <= ST_IDLE; 98 | else 99 | fsm_state <= fsm_state_next; 100 | 101 | // Next-State logic 102 | always @(*) 103 | begin 104 | // Default is to not move 105 | fsm_state_next = fsm_state; 106 | 107 | // Transistions ? 108 | case (fsm_state) 109 | ST_IDLE: 110 | if (scan_go_in & ~init_done) 111 | fsm_state_next = ST_WAIT; 112 | 113 | ST_WAIT: 114 | if (bcm_rdy_in & shift_rdy_in & blank_rdy_in) 115 | fsm_state_next = ST_SHIFT; 116 | 117 | ST_SHIFT: 118 | if (col_last & reg_sel) 119 | fsm_state_next = ST_GO; 120 | 121 | ST_GO: 122 | fsm_state_next = ST_IDLE; 123 | endcase 124 | end 125 | 126 | 127 | // Control 128 | // ------- 129 | 130 | always @(posedge clk) 131 | if (rst) 132 | init_done <= 1'b0; 133 | else 134 | init_done <= (init_done | (fsm_state == ST_GO)) & ~init_req; 135 | 136 | // External handshake 137 | assign bcm_rdy_out = (fsm_state == ST_IDLE) & bcm_rdy_in; 138 | 139 | 140 | // Init sequence shift 141 | // ------------------- 142 | 143 | // Flag 144 | assign active = (fsm_state == ST_SHIFT); 145 | 146 | // Column counter 147 | assign col_rst = col_last | (fsm_state != ST_SHIFT); 148 | 149 | always @(posedge clk) 150 | if (col_rst) begin 151 | col_cnt <= N_COLS - 17; 152 | col_last <= 1'b0; 153 | col_le <= 1'b0; 154 | end else begin 155 | col_cnt <= col_cnt - 1; 156 | col_last <= col_cnt[LOG_N_COLS] & (col_cnt[3:0] == 4'h1); 157 | col_le <= col_cnt[LOG_N_COLS] & (col_cnt[3:0] < (reg_sel ? 4'hd : 4'hc)); 158 | end 159 | 160 | // Reg select 161 | always @(posedge clk) 162 | reg_sel <= (fsm_state == ST_SHIFT) ? (reg_sel ^ col_last) : 1'b0; 163 | 164 | // ROM 165 | assign reg_bit[0] = INIT_R1[col_cnt[3:0]]; 166 | assign reg_bit[1] = INIT_R2[col_cnt[3:0]]; 167 | 168 | // Outputs 169 | assign inject_data = reg_bit[reg_sel]; 170 | assign inject_le = col_le; 171 | 172 | 173 | // PHY signal injection 174 | // -------------------- 175 | 176 | always @(posedge clk) 177 | begin 178 | phy_out_addr_inc <= ~active ? phy_in_addr_inc : 1'b0; 179 | phy_out_addr_rst <= ~active ? phy_in_addr_rst : 1'b0; 180 | phy_out_addr <= ~active ? phy_in_addr : { LOG_N_ROWS{1'b0} }; 181 | phy_out_data <= ~active ? phy_in_data : { SDW{inject_data} }; 182 | phy_out_clk <= ~active ? phy_in_clk : 1'b1; 183 | phy_out_le <= ~active ? phy_in_le : inject_le; 184 | phy_out_blank <= ~active ? phy_in_blank : 1'b1; 185 | end 186 | 187 | endmodule 188 | -------------------------------------------------------------------------------- /rtl/hub75_fb_writein.v: -------------------------------------------------------------------------------- 1 | /* 2 | * hub75_fb_writein.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2019-2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_fb_writein #( 13 | parameter integer N_BANKS = 2, 14 | parameter integer N_ROWS = 32, 15 | parameter integer N_COLS = 64, 16 | parameter integer BITDEPTH = 24, 17 | parameter integer FB_AW = 13, 18 | parameter integer FB_DW = 16, 19 | parameter integer FB_DC = 2, 20 | 21 | // Auto-set 22 | parameter integer LOG_N_BANKS = $clog2(N_BANKS), 23 | parameter integer LOG_N_ROWS = $clog2(N_ROWS), 24 | parameter integer LOG_N_COLS = $clog2(N_COLS) 25 | )( 26 | // Write interface - Row store/swap 27 | input wire [LOG_N_BANKS-1:0] wr_bank_addr, 28 | input wire [LOG_N_ROWS-1:0] wr_row_addr, 29 | input wire wr_row_store, 30 | output wire wr_row_rdy, 31 | input wire wr_row_swap, 32 | 33 | // Write interface - Access 34 | input wire [BITDEPTH-1:0] wr_data, 35 | input wire [LOG_N_COLS-1:0] wr_col_addr, 36 | input wire wr_en, 37 | 38 | // Write In - Control 39 | output wire ctrl_req, 40 | input wire ctrl_gnt, 41 | output reg ctrl_rel, 42 | 43 | // Write In - Frame Buffer Access 44 | output wire [FB_AW-1:0] fb_addr, 45 | output wire [FB_DW-1:0] fb_data, 46 | output wire fb_wren, 47 | 48 | // Clock / Reset 49 | input wire clk, 50 | input wire rst 51 | ); 52 | 53 | // Counter = [ col_addr : dc_idx ] 54 | localparam integer CS = $clog2(FB_DC); 55 | localparam integer CW = LOG_N_COLS + CS; 56 | 57 | 58 | // Signals 59 | // ------- 60 | 61 | // Write-in process 62 | reg wip_buf; 63 | 64 | reg wip_pending; 65 | reg wip_running; 66 | reg wip_ready; 67 | 68 | reg [LOG_N_BANKS-1:0] wip_bank_addr; 69 | reg [LOG_N_ROWS-1:0] wip_row_addr; 70 | 71 | reg [CW-1:0] wip_cnt; 72 | reg wip_last; 73 | 74 | // Line buffer access 75 | wire [LOG_N_COLS-1:0] wilb_col_addr; 76 | wire [BITDEPTH-1:0] wilb_data; 77 | wire wilb_rden; 78 | 79 | wire [FB_DW*FB_DC-1:0] wilb_data_ext; 80 | 81 | // Frame buffer access 82 | reg [FB_AW-1:0] fb_addr_i; 83 | reg fb_wren_i; 84 | 85 | 86 | // Control 87 | // ------- 88 | 89 | // Buffer swap 90 | always @(posedge clk or posedge rst) 91 | if (rst) 92 | wip_buf <= 1'b0; 93 | else 94 | wip_buf <= wip_buf ^ wr_row_swap; 95 | 96 | // Track status and requests 97 | always @(posedge clk or posedge rst) 98 | if (rst) begin 99 | wip_pending <= 1'b0; 100 | wip_running <= 1'b0; 101 | wip_ready <= 1'b1; 102 | end else begin 103 | wip_pending <= (wip_pending & ~ctrl_gnt) | wr_row_store; 104 | wip_running <= (wip_running & ~wip_last) | ctrl_gnt; 105 | wip_ready <= (wip_ready | wip_last) & ~wr_row_store; 106 | end 107 | 108 | // Arbiter interface 109 | assign ctrl_req = wip_pending; 110 | 111 | always @(posedge clk) 112 | ctrl_rel <= wip_last; 113 | 114 | // Write interface 115 | assign wr_row_rdy = wip_ready; 116 | 117 | // Latch bank/row address 118 | always @(posedge clk) 119 | if (wr_row_store) begin 120 | wip_bank_addr <= wr_bank_addr; 121 | wip_row_addr <= wr_row_addr; 122 | end 123 | 124 | // Counter 125 | always @(posedge clk) 126 | if (~wip_running) begin 127 | wip_cnt <= 0; 128 | wip_last <= 1'b0; 129 | end else begin 130 | wip_cnt <= wip_cnt + 1; 131 | wip_last <= wip_cnt == ((N_COLS << CS) - 2); 132 | end 133 | 134 | 135 | // Line buffer 136 | // ----------- 137 | 138 | hub75_linebuffer #( 139 | .N_WORDS(1), 140 | .WORD_WIDTH(BITDEPTH), 141 | .ADDR_WIDTH(1 + LOG_N_COLS) 142 | ) writein_buf_I ( 143 | .wr_addr({~wip_buf, wr_col_addr}), 144 | .wr_data(wr_data), 145 | .wr_mask(1'b1), 146 | .wr_ena(wr_en), 147 | .rd_addr({wip_buf, wilb_col_addr}), 148 | .rd_data(wilb_data), 149 | .rd_ena(wilb_rden), 150 | .clk(clk) 151 | ); 152 | 153 | 154 | // Line buffer -> Frame buffer 155 | // --------------------------- 156 | 157 | // Line buffer read 158 | assign wilb_col_addr = wip_cnt[CW-1:CS]; 159 | assign wilb_rden = wip_running; 160 | 161 | // Route data from frame buffer to line buffer 162 | // Extend it to a multiple of the frame buffer data width 163 | assign wilb_data_ext = { {(FB_DW*FB_DC-BITDEPTH){1'b0}}, wilb_data }; 164 | 165 | // Mux 166 | generate 167 | if (CS > 0) 168 | assign fb_data = wilb_data_ext[FB_DW*fb_addr_i[CS-1:0]+:FB_DW]; 169 | else 170 | assign fb_data = wilb_data_ext; 171 | endgenerate 172 | 173 | // Sync FB command with the read data from line buffer (1 cycle delay) 174 | always @(posedge clk) 175 | begin 176 | fb_addr_i[FB_AW-1:CS] <= { wip_row_addr, wip_cnt[CW-1:CS], wip_bank_addr }; 177 | if (CS > 0) 178 | fb_addr_i[CS-1:0] <= wip_cnt[CS-1:0]; 179 | fb_wren_i <= wip_running; 180 | end 181 | 182 | assign fb_addr = fb_addr_i; 183 | assign fb_wren = fb_wren_i; 184 | 185 | endmodule // hub75_fb_writein 186 | -------------------------------------------------------------------------------- /rtl/hub75_bcm.v: -------------------------------------------------------------------------------- 1 | /* 2 | * hub75_bcm.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2019-2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_bcm #( 13 | parameter integer N_ROWS = 32, 14 | parameter integer N_PLANES = 8, 15 | 16 | // Auto-set 17 | parameter integer LOG_N_ROWS = $clog2(N_ROWS) 18 | )( 19 | // PHY 20 | output wire phy_addr_inc, 21 | output wire phy_addr_rst, 22 | output wire [LOG_N_ROWS-1:0] phy_addr, 23 | output wire phy_le, 24 | 25 | // Shifter interface 26 | output wire [N_PLANES-1:0] shift_plane, 27 | output wire shift_go, 28 | input wire shift_rdy, 29 | 30 | // Blanking interface 31 | output wire [N_PLANES-1:0] blank_plane, 32 | output wire blank_go, 33 | input wire blank_rdy, 34 | 35 | // Control 36 | input wire [LOG_N_ROWS-1:0] ctrl_row, 37 | input wire ctrl_row_first, 38 | input wire ctrl_go, 39 | output wire ctrl_rdy, 40 | 41 | // Config 42 | input wire [7:0] cfg_pre_latch_len, 43 | input wire [7:0] cfg_latch_len, 44 | input wire [7:0] cfg_post_latch_len, 45 | 46 | // Clock / Reset 47 | input wire clk, 48 | input wire rst 49 | ); 50 | 51 | genvar i; 52 | 53 | // Signals 54 | // ------- 55 | 56 | // FSM 57 | localparam 58 | ST_IDLE = 0, 59 | ST_SHIFT = 1, 60 | ST_WAIT_TO_LATCH = 2, 61 | ST_PRE_LATCH = 3, 62 | ST_DO_LATCH = 4, 63 | ST_POST_LATCH = 5, 64 | ST_ISSUE_BLANK = 6; 65 | 66 | reg [2:0] fsm_state; 67 | reg [2:0] fsm_state_next; 68 | 69 | reg [7:0] timer_val; 70 | wire timer_trig; 71 | 72 | reg [N_PLANES-1:0] plane; 73 | wire plane_last; 74 | 75 | reg [LOG_N_ROWS-1:0] addr; 76 | reg [LOG_N_ROWS-1:0] addr_out; 77 | reg addr_do_inc; 78 | reg addr_do_rst; 79 | wire le; 80 | 81 | 82 | // FSM 83 | // --- 84 | 85 | // State register 86 | always @(posedge clk or posedge rst) 87 | if (rst) 88 | fsm_state <= ST_IDLE; 89 | else 90 | fsm_state <= fsm_state_next; 91 | 92 | // Next-State logic 93 | always @(*) 94 | begin 95 | // Default is to not move 96 | fsm_state_next = fsm_state; 97 | 98 | // Transitions ? 99 | case (fsm_state) 100 | ST_IDLE: 101 | if (ctrl_go) 102 | fsm_state_next = ST_SHIFT; 103 | 104 | ST_SHIFT: 105 | fsm_state_next = ST_WAIT_TO_LATCH; 106 | 107 | ST_WAIT_TO_LATCH: 108 | if (shift_rdy & blank_rdy) 109 | fsm_state_next = ST_PRE_LATCH; 110 | 111 | ST_PRE_LATCH: 112 | if (timer_trig) 113 | fsm_state_next = ST_DO_LATCH; 114 | 115 | ST_DO_LATCH: 116 | if (timer_trig) 117 | fsm_state_next = ST_POST_LATCH; 118 | 119 | ST_POST_LATCH: 120 | if (timer_trig) 121 | fsm_state_next = ST_ISSUE_BLANK; 122 | 123 | ST_ISSUE_BLANK: 124 | fsm_state_next = plane_last ? ST_IDLE : ST_SHIFT; 125 | endcase 126 | end 127 | 128 | 129 | // Timer 130 | // ----- 131 | 132 | always @(posedge clk) 133 | begin 134 | if (fsm_state != fsm_state_next) begin 135 | // Default is to trigger all the time 136 | timer_val <= 8'h80; 137 | 138 | // Preload for next state 139 | case (fsm_state_next) 140 | ST_PRE_LATCH: timer_val <= cfg_pre_latch_len; 141 | ST_DO_LATCH: timer_val <= cfg_latch_len; 142 | ST_POST_LATCH: timer_val <= cfg_post_latch_len; 143 | endcase 144 | end else begin 145 | timer_val <= timer_val - 1; 146 | end 147 | end 148 | 149 | assign timer_trig = timer_val[7]; 150 | 151 | 152 | // Plane counter 153 | // ------------- 154 | 155 | always @(posedge clk) 156 | if (fsm_state == ST_IDLE) 157 | plane <= { {(N_PLANES-1){1'b0}}, 1'b1 }; 158 | else if (fsm_state == ST_ISSUE_BLANK) 159 | plane <= { plane[N_PLANES-2:0], 1'b0 }; 160 | 161 | assign plane_last = plane[N_PLANES-1]; 162 | 163 | 164 | // External Control 165 | // ---------------- 166 | 167 | // Shifter 168 | assign shift_plane = plane; 169 | assign shift_go = (fsm_state == ST_SHIFT); 170 | 171 | // Blanking 172 | assign blank_plane = plane; 173 | assign blank_go = (fsm_state == ST_ISSUE_BLANK); 174 | 175 | // Address 176 | always @(posedge clk) 177 | if (ctrl_go) 178 | addr <= ctrl_row; 179 | 180 | always @(posedge clk) 181 | begin 182 | addr_do_inc <= (addr_do_inc | (ctrl_go & ~ctrl_row_first)) & ~(fsm_state == ST_POST_LATCH); 183 | addr_do_rst <= (addr_do_rst | (ctrl_go & ctrl_row_first)) & ~(fsm_state == ST_POST_LATCH); 184 | end 185 | 186 | always @(posedge clk) 187 | if (fsm_state == ST_DO_LATCH) 188 | addr_out <= addr; 189 | 190 | // Latch 191 | assign le = (fsm_state == ST_DO_LATCH); 192 | 193 | // Ready ? 194 | assign ctrl_rdy = (fsm_state == ST_IDLE); 195 | 196 | 197 | // PHY 198 | // --- 199 | 200 | assign phy_addr = addr_out; 201 | assign phy_le = le; 202 | 203 | assign phy_addr_inc = (fsm_state == ST_DO_LATCH) ? addr_do_inc : 1'b0; 204 | assign phy_addr_rst = (fsm_state == ST_DO_LATCH) ? addr_do_rst : 1'b0; 205 | 206 | endmodule // hub75_bcm 207 | -------------------------------------------------------------------------------- /rtl/hub75_phy_ddr.v: -------------------------------------------------------------------------------- 1 | /* 2 | * hub75_phy_ddr.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2019-2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_phy_ddr #( 13 | parameter integer N_BANKS = 2, 14 | parameter integer N_ROWS = 32, 15 | parameter integer N_CHANS = 3, 16 | parameter integer PHY_N = 1, // # of PHY in // 17 | parameter integer PHY_AIR = 0, // PHY Address Inc/Reset 18 | parameter integer PHY_DDR = 1, // PHY DDR Phase 19 | 20 | // Auto-set 21 | parameter integer SDW = N_BANKS * N_CHANS, 22 | parameter integer ESDW = SDW / 2, 23 | parameter integer LOG_N_ROWS = $clog2(N_ROWS) 24 | )( 25 | // Hub75 interface pads 26 | output wire [PHY_N-1:0] hub75_addr_inc, 27 | output wire [PHY_N-1:0] hub75_addr_rst, 28 | output wire [(PHY_N*LOG_N_ROWS)-1:0] hub75_addr, 29 | output wire [ESDW-1 :0] hub75_data, 30 | output wire [PHY_N-1:0] hub75_clk, 31 | output wire [PHY_N-1:0] hub75_le, 32 | output wire [PHY_N-1:0] hub75_blank, 33 | 34 | // PHY interface signals 35 | input wire phy_addr_inc, 36 | input wire phy_addr_rst, 37 | input wire [LOG_N_ROWS-1:0] phy_addr, 38 | input wire [SDW-1:0] phy_data, 39 | input wire phy_clk, 40 | input wire phy_le, 41 | input wire phy_blank, 42 | 43 | // Clock / Reset 44 | input wire clk, 45 | input wire clk_2x, 46 | input wire rst 47 | ); 48 | // Signals 49 | // ------- 50 | 51 | // Sync 52 | reg sync_toggle; 53 | reg sync_done; 54 | reg [1:0] sync_cap; 55 | reg [1:0] sync; // [0] in phase with clk, [1] is clk_n 56 | 57 | // Cross-clock 58 | reg cc_addr_inc; 59 | reg cc_addr_rst; 60 | reg [LOG_N_ROWS-1:0] cc_addr; 61 | reg [(N_BANKS*N_CHANS)-1:0] cc_data; 62 | reg cc_clk; 63 | reg cc_le; 64 | reg cc_blank; 65 | 66 | // Data Mux 67 | wire [ESDW-1:0] mux_data; 68 | 69 | // External Shift clock 70 | reg clk_sig; 71 | 72 | 73 | // Capture signals in 2x domain 74 | // ---------------------------- 75 | 76 | // Sync signals 77 | always @(posedge clk or posedge rst) 78 | if (rst) 79 | sync_toggle <= 1'b0; 80 | else 81 | sync_toggle <= ~sync_toggle; 82 | 83 | always @(posedge clk_2x or posedge rst) 84 | begin 85 | if (rst) begin 86 | sync_done <= 1'b0; 87 | sync_cap <= 2'b00; 88 | sync <= 2'b00; 89 | end else begin 90 | sync_done <= sync_done | (sync_cap[0] ^ sync_cap[1]); 91 | sync_cap <= { sync_cap[0], sync_toggle }; 92 | sync[0] <= sync_done ? ~sync[0] : (sync_cap[0] ^ sync_cap[1]); 93 | sync[1] <= sync[0]; 94 | end 95 | end 96 | 97 | // Capture 98 | always @(posedge clk_2x or posedge rst) 99 | begin 100 | if (rst) begin 101 | cc_addr_inc <= 1'b0; 102 | cc_addr_rst <= 1'b0; 103 | cc_addr <= 0; 104 | cc_data <= 0; 105 | cc_clk <= 1'b0; 106 | cc_le <= 1'b0; 107 | cc_blank <= 1'b0; 108 | end else if (sync[0]) begin 109 | cc_addr_inc <= phy_addr_inc ^ PHY_AIR[1]; 110 | cc_addr_rst <= phy_addr_rst ^ PHY_AIR[2]; 111 | cc_addr <= phy_addr; 112 | cc_data <= phy_data; 113 | cc_clk <= phy_clk; 114 | cc_le <= phy_le; 115 | cc_blank <= phy_blank; 116 | end 117 | end 118 | 119 | 120 | // IOB 121 | // --- 122 | 123 | // Address 124 | genvar i; 125 | generate 126 | if (PHY_AIR == 0) begin 127 | for (i=0; i 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_fb_readout #( 13 | parameter integer N_BANKS = 2, 14 | parameter integer N_ROWS = 32, 15 | parameter integer N_COLS = 64, 16 | parameter integer N_CHANS = 3, 17 | parameter integer N_PLANES = 8, 18 | parameter integer BITDEPTH = 24, 19 | parameter integer FB_AW = 13, 20 | parameter integer FB_DW = 16, 21 | parameter integer FB_DC = 2, 22 | 23 | // Auto-set 24 | parameter integer LOG_N_BANKS = $clog2(N_BANKS), 25 | parameter integer LOG_N_ROWS = $clog2(N_ROWS), 26 | parameter integer LOG_N_COLS = $clog2(N_COLS) 27 | )( 28 | // Read interface - Preload 29 | input wire [LOG_N_ROWS-1:0] rd_row_addr, 30 | input wire rd_row_load, 31 | output wire rd_row_rdy, 32 | input wire rd_row_swap, 33 | 34 | // Read interface - Access 35 | output wire [(N_BANKS * N_CHANS * N_PLANES)-1:0] rd_data, 36 | input wire [LOG_N_COLS-1:0] rd_col_addr, 37 | input wire rd_en, 38 | 39 | // Read Out - Control 40 | output wire ctrl_req, 41 | input wire ctrl_gnt, 42 | output reg ctrl_rel, 43 | 44 | // Read Out - Frame Buffer Access 45 | output wire [FB_AW-1:0] fb_addr, 46 | input wire [FB_DW-1:0] fb_data, 47 | 48 | // Clock / Reset 49 | input wire clk, 50 | input wire rst 51 | ); 52 | 53 | // Counter = [ col_addr : bank_addr : dc_idx ] 54 | localparam integer CS1 = $clog2(FB_DC); 55 | localparam integer CS2 = CS1 + LOG_N_BANKS; 56 | localparam integer CW = CS2 + LOG_N_COLS; 57 | 58 | 59 | // Signals 60 | // ------- 61 | 62 | // Read-out process 63 | reg rop_buf; 64 | 65 | reg rop_pending; 66 | reg rop_running; 67 | reg rop_ready; 68 | 69 | reg [LOG_N_ROWS-1:0] rop_row_addr; 70 | 71 | reg [CW-1:0] rop_cnt; 72 | reg rop_last; 73 | 74 | wire rop_move; 75 | wire rop_done; 76 | 77 | // Frame buffer access 78 | wire fb_rden; 79 | 80 | reg fb_rden_r; 81 | reg [FB_DW-1:0] fb_data_save; 82 | wire [FB_DW-1:0] fb_data_mux; 83 | reg [(FB_DC*FB_DW)-1:0] fb_data_ext; 84 | 85 | // Color Mapper 86 | reg [CW-CS1-1:0] cm_in_user_addr_pre; 87 | reg cm_in_user_last_pre; 88 | reg cm_in_valid_pre; 89 | 90 | wire [BITDEPTH-1:0] cm_in_data; 91 | reg [CW-CS1-1:0] cm_in_user_addr; 92 | reg cm_in_user_last; 93 | reg cm_in_valid; 94 | wire cm_in_ready; 95 | 96 | wire [(N_CHANS*N_PLANES)-1:0] cm_out_data; 97 | wire [CW-CS1-1:0] cm_out_user_addr; 98 | wire cm_out_user_last; 99 | wire cm_out_valid; 100 | 101 | // Line buffer access 102 | wire [(N_BANKS * N_CHANS * N_PLANES)-1:0] rolb_wr_data; 103 | wire [N_BANKS-1:0] rolb_wr_mask; 104 | wire [LOG_N_COLS-1:0] rolb_wr_addr; 105 | wire rolb_wr_ena; 106 | 107 | 108 | // Control 109 | // ------- 110 | 111 | // Buffer swap 112 | always @(posedge clk or posedge rst) 113 | if (rst) 114 | rop_buf <= 1'b0; 115 | else 116 | rop_buf <= rop_buf ^ rd_row_swap; 117 | 118 | // Track status and requests 119 | always @(posedge clk or posedge rst) 120 | if (rst) begin 121 | rop_pending <= 1'b0; 122 | rop_running <= 1'b0; 123 | rop_ready <= 1'b0; 124 | end else begin 125 | rop_pending <= (rop_pending & ~ctrl_gnt) | rd_row_load; 126 | rop_running <= (rop_running & ~rop_done) | ctrl_gnt; 127 | rop_ready <= (rop_ready | rop_done) & ~rd_row_load; 128 | end 129 | 130 | // Arbiter interface 131 | assign ctrl_req = rop_pending; 132 | 133 | always @(posedge clk) 134 | ctrl_rel <= cm_out_valid & cm_out_user_last; 135 | 136 | // Read interface 137 | assign rd_row_rdy = rop_ready; 138 | 139 | // Latch row address 140 | always @(posedge clk) 141 | if (rd_row_load) 142 | rop_row_addr <= rd_row_addr; 143 | 144 | // Counter 145 | always @(posedge clk or negedge rop_running) 146 | if (~rop_running) begin 147 | rop_cnt <= 0; 148 | rop_last <= 1'b0; 149 | end else if (rop_move) begin 150 | rop_cnt <= rop_cnt + 1; 151 | rop_last <= rop_cnt == ((N_COLS << CS2) - 2); 152 | end 153 | 154 | assign rop_done = rop_last & rop_move; 155 | 156 | // Move pipeline ahead 157 | assign rop_move = ~cm_in_valid | cm_in_ready; 158 | 159 | 160 | // Line buffer 161 | // ----------- 162 | 163 | hub75_linebuffer #( 164 | .N_WORDS(N_BANKS), 165 | .WORD_WIDTH(N_CHANS * N_PLANES), 166 | .ADDR_WIDTH(1 + LOG_N_COLS) 167 | ) readout_buf_I ( 168 | .wr_addr({~rop_buf, rolb_wr_addr}), 169 | .wr_data(rolb_wr_data), 170 | .wr_mask(rolb_wr_mask), 171 | .wr_ena(rolb_wr_ena), 172 | .rd_addr({rop_buf, rd_col_addr}), 173 | .rd_data(rd_data), 174 | .rd_ena(rd_en), 175 | .clk(clk) 176 | ); 177 | 178 | 179 | // Frame buffer -> Color mapper 180 | // ---------------------------- 181 | 182 | // Frame buffer read 183 | assign fb_addr = { rop_row_addr, rop_cnt }; 184 | assign fb_rden = rop_move; 185 | 186 | // Simulate a 'READ ENABLE' on the frame buffer by saving the previous 187 | // data and muxing 188 | always @(posedge clk) 189 | fb_rden_r <= fb_rden; 190 | 191 | always @(posedge clk) 192 | if (fb_rden_r) 193 | fb_data_save <= fb_data; 194 | 195 | assign fb_data_mux = fb_rden_r ? fb_data : fb_data_save; 196 | 197 | // Shift register of frame buffer words to reconstruct and entire 198 | // 'BITDEPTH' worth of bits. 199 | always @(posedge clk) 200 | if (rop_move) 201 | if (FB_DC > 1) 202 | fb_data_ext <= { fb_data_mux, fb_data_ext[(FB_DC*FB_DW)-1:FB_DW] }; 203 | else 204 | fb_data_ext <= { fb_data_mux }; 205 | 206 | // Map to the color mapper input 207 | assign cm_in_data = fb_data_ext[BITDEPTH-1:0]; 208 | 209 | always @(posedge clk or posedge rst) 210 | if (rst) begin 211 | cm_in_valid_pre <= 1'b0; 212 | cm_in_valid <= 1'b0; 213 | end else if (rop_move) begin 214 | if (CS1 > 0) 215 | cm_in_valid_pre <= rop_running & &rop_cnt[CS1-1:0]; 216 | else 217 | cm_in_valid_pre <= rop_running; 218 | 219 | cm_in_valid <= cm_in_valid_pre; 220 | end 221 | 222 | always @(posedge clk) 223 | if (rop_move) begin 224 | // This is synced with the RAM output 225 | cm_in_user_addr_pre <= rop_cnt[CW-1:CS1]; 226 | cm_in_user_last_pre <= rop_last; 227 | 228 | // This is synced with the fb_data_ext signal 229 | cm_in_user_addr <= cm_in_user_addr_pre; 230 | cm_in_user_last <= cm_in_user_last_pre; 231 | end 232 | 233 | 234 | // Color mapping core 235 | // ------------------ 236 | 237 | hub75_colormap #( 238 | .N_CHANS(N_CHANS), 239 | .N_PLANES(N_PLANES), 240 | .BITDEPTH(BITDEPTH), 241 | .USER_WIDTH(CW-CS1+1) 242 | ) cm_I ( 243 | .in_data(cm_in_data), 244 | .in_user({cm_in_user_addr, cm_in_user_last}), 245 | .in_valid(cm_in_valid), 246 | .in_ready(cm_in_ready), 247 | .out_data(cm_out_data), 248 | .out_user({cm_out_user_addr, cm_out_user_last}), 249 | .out_valid(cm_out_valid), 250 | .clk(clk), 251 | .rst(rst) 252 | ); 253 | 254 | 255 | // Color mapper -> Line buffer 256 | // --------------------------- 257 | 258 | genvar i; 259 | 260 | assign rolb_wr_data = { (N_BANKS){cm_out_data} }; 261 | assign rolb_wr_addr = cm_out_user_addr[CW-CS1-1:CS2-CS1]; 262 | assign rolb_wr_ena = cm_out_valid; 263 | 264 | generate 265 | if (N_BANKS > 1) 266 | for (i=0; i 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_framebuffer #( 13 | parameter integer N_BANKS = 2, 14 | parameter integer N_ROWS = 32, 15 | parameter integer N_COLS = 64, 16 | parameter integer N_CHANS = 3, 17 | parameter integer N_PLANES = 8, 18 | parameter integer BITDEPTH = 24, 19 | 20 | // Auto-set 21 | parameter integer LOG_N_BANKS = $clog2(N_BANKS), 22 | parameter integer LOG_N_ROWS = $clog2(N_ROWS), 23 | parameter integer LOG_N_COLS = $clog2(N_COLS) 24 | )( 25 | // Write interface - Row store/swap 26 | input wire [LOG_N_BANKS-1:0] wr_bank_addr, 27 | input wire [LOG_N_ROWS-1:0] wr_row_addr, 28 | input wire wr_row_store, 29 | output wire wr_row_rdy, 30 | input wire wr_row_swap, 31 | 32 | // Write interface - Access 33 | input wire [BITDEPTH-1:0] wr_data, 34 | input wire [LOG_N_COLS-1:0] wr_col_addr, 35 | input wire wr_en, 36 | 37 | // Read interface - Preload 38 | input wire [LOG_N_ROWS-1:0] rd_row_addr, 39 | input wire rd_row_load, 40 | output wire rd_row_rdy, 41 | input wire rd_row_swap, 42 | 43 | // Read interface - Access 44 | output wire [(N_BANKS * N_CHANS * N_PLANES)-1:0] rd_data, 45 | input wire [LOG_N_COLS-1:0] rd_col_addr, 46 | input wire rd_en, 47 | 48 | // Frame swap request 49 | input wire frame_swap, 50 | 51 | // Clock / Reset 52 | input wire clk, 53 | input wire rst 54 | ); 55 | // Internal params 56 | // --------------- 57 | // This tries to come up with the best memory layout and sets up 58 | // a bunch of constants appropriately 59 | // 60 | // Address seen from access PoV ( fb_addr signal ) : 61 | // 1 : Buffer Select 62 | // LOG_N_BANKS : Bank selection 63 | // LOG_N_ROWS : Row address 64 | // LOG_N_COLS : Column address 65 | // LOG_FB_DC : Index of the word that compose the full color data 66 | // (when framebuffer width is thinner than BITDEPTH) 67 | // ------------- 68 | // FB_AW : (total number of bit in that address) 69 | // 70 | // To map this to the memory address ( mem_addr signal ): 71 | // * Add PAD_BITS at the MSBs (just in case we use less than 1 SPRAM) 72 | // * Drop OMUX_BITS + IMUX_BITS at the LSBs 73 | // - IMUX_BITS used for muxing down the 16 bit wide bus down to 74 | // FB_DW if needed 75 | // - OMUX_BITS used to mux between the SPRAMs used in // to 76 | // increase the total memory depth 77 | 78 | `define MIN(_a, _b) ((_a) < (_b) ? (_a) : (_b)) 79 | `define MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b)) 80 | 81 | // Round bitdepth to a power of 2 with minimum of 4 82 | localparam integer LOG_BITDEPTH = (BITDEPTH > 4) ? $clog2(BITDEPTH) : 2; 83 | 84 | // Number of SPRAM needed for frame buffer 85 | localparam integer LOG_SPRAM_COUNT = `MAX(0, (1 + LOG_N_BANKS + LOG_N_ROWS + LOG_N_COLS + LOG_BITDEPTH) - 18); 86 | localparam integer SPRAM_COUNT = 1 << LOG_SPRAM_COUNT; 87 | 88 | // Width of the framebuffer access bus 89 | localparam integer FB_DW = `MIN((16 * SPRAM_COUNT), (1 << LOG_BITDEPTH)); 90 | 91 | // Number of SPRAM used in 'width-mode' 92 | localparam integer LOG_SPRAM_WIDE = $clog2(`MAX(FB_DW,16)) - 4; 93 | localparam integer SPRAM_WIDE = 1 << LOG_SPRAM_WIDE; 94 | 95 | // Number of SPRAM used in 'depth-mode' 96 | localparam integer LOG_SPRAM_DEEP = LOG_SPRAM_COUNT - LOG_SPRAM_WIDE; 97 | localparam integer SPRAM_DEEP = 1 << LOG_SPRAM_DEEP; 98 | 99 | // Number of framebuffer words for each pixel 100 | localparam integer LOG_FB_DC = LOG_BITDEPTH - $clog2(FB_DW); 101 | localparam integer FB_DC = 1 << LOG_FB_DC; 102 | 103 | // Framebuffer final address width 104 | localparam integer FB_AW = 1 + LOG_N_BANKS + LOG_N_ROWS + LOG_N_COLS + LOG_FB_DC; 105 | 106 | // Zero-bits to MSB pad SPRAM address (if using less than 1 SPRAM) 107 | localparam integer PAD_BITS = `MAX(0, 18 - (1 + LOG_N_BANKS + LOG_N_ROWS + LOG_N_COLS + LOG_BITDEPTH)); 108 | 109 | // Number of bits used for muxing inside the wide memory bus down to FB_DW 110 | localparam integer IMUX_BITS = $clog2(`MAX(1, 16 / FB_DW)); 111 | 112 | // Number of bits used for muxing between the SPRAM used in // to increase depth 113 | localparam integer OMUX_BITS = LOG_SPRAM_DEEP; 114 | 115 | initial begin 116 | $display("Hub75 Frame Buffer config :"); 117 | $display(" - SPRAM_COUNT : %d", SPRAM_COUNT); 118 | $display(" - SPRAM_WIDE : %d", SPRAM_WIDE); 119 | $display(" - SPRAM_DEEP : %d", SPRAM_DEEP); 120 | $display(" - FB_AW : %d", FB_AW); 121 | $display(" - FB_DW : %d", FB_DW); 122 | $display(" - FB_DC : %d", FB_DC); 123 | $display(" - PAD_BITS : %d", PAD_BITS); 124 | $display(" - IMUX_BITS : %d", IMUX_BITS); 125 | $display(" - OMUX_BITS : %d", OMUX_BITS); 126 | end 127 | 128 | 129 | // Signals 130 | // ------- 131 | 132 | // Arbitration logic 133 | reg arb_busy; 134 | reg arb_prio; 135 | 136 | // Write-in control 137 | wire wi_req; 138 | reg wi_gnt; 139 | wire wi_rel; 140 | 141 | // Read-out control 142 | wire ro_req; 143 | reg ro_gnt; 144 | wire ro_rel; 145 | 146 | // Raw signals from the storage cells 147 | wire [16*SPRAM_WIDE-1:0] mem_di; 148 | wire [16*SPRAM_WIDE-1:0] mem_do [0:SPRAM_DEEP-1]; 149 | wire [13:0] mem_addr; 150 | wire [ 3:0] mem_mask; 151 | wire mem_wren [0:SPRAM_DEEP-1]; 152 | 153 | wire [16*SPRAM_WIDE-1:0] mem_do_mux; 154 | 155 | 156 | // Frame buffer access 157 | wire [FB_DW-1:0] fb_di; 158 | wire [FB_DW-1:0] fb_do; 159 | wire [FB_AW-1:0] fb_addr; 160 | wire fb_wren; 161 | 162 | reg [FB_AW-1:0] fb_addr_r; 163 | reg fb_pingpong; 164 | 165 | // Write-in frame buffer access 166 | wire [FB_AW-2:0] wifb_addr; 167 | wire [FB_DW-1:0] wifb_data; 168 | wire wifb_wren; 169 | 170 | // Read-out frame-buffer access 171 | wire [FB_AW-2:0] rofb_addr; 172 | wire [FB_DW-1:0] rofb_data; 173 | 174 | 175 | // Control 176 | // ------- 177 | 178 | // Arbitration logic 179 | always @(posedge clk or posedge rst) 180 | begin 181 | if (rst) begin 182 | arb_prio <= 1'b0; 183 | arb_busy <= 1'b0; 184 | wi_gnt <= 1'b0; 185 | ro_gnt <= 1'b0; 186 | end else begin 187 | arb_busy <= (arb_busy | wi_req | ro_req) & ~(wi_rel | ro_rel); 188 | arb_prio <= (wi_gnt | ro_gnt) ? ro_gnt : arb_prio; 189 | wi_gnt <= ~arb_busy & wi_req & (~ro_req | arb_prio); 190 | ro_gnt <= ~arb_busy & ro_req & (~wi_req | ~arb_prio); 191 | end 192 | end 193 | 194 | // Double-Buffer 195 | always @(posedge clk or posedge rst) 196 | if (rst) 197 | fb_pingpong <= 1'b0; 198 | else 199 | fb_pingpong <= fb_pingpong ^ frame_swap; 200 | 201 | // Shared access 202 | // We assume users as well behaved and just use wren for mux control 203 | assign fb_di = wifb_data; 204 | assign rofb_data = fb_do; 205 | assign fb_addr = wifb_wren ? { ~fb_pingpong, wifb_addr } : { fb_pingpong, rofb_addr }; 206 | assign fb_wren = wifb_wren; 207 | 208 | 209 | // Storage 210 | // ------- 211 | 212 | genvar i, j; 213 | 214 | // Generate memory elements 215 | generate 216 | for (i=0; i mem_addr 243 | assign mem_addr = { {(PAD_BITS){1'b0}}, fb_addr[FB_AW-1:OMUX_BITS+IMUX_BITS] }; 244 | 245 | // Output muxing 246 | generate 247 | // Mux across the SPRAM used in parallel for depth (if needed) 248 | if (OMUX_BITS > 0) 249 | assign mem_do_mux = mem_do[fb_addr_r[OMUX_BITS+IMUX_BITS-1:IMUX_BITS]]; 250 | else 251 | assign mem_do_mux = mem_do[0]; 252 | 253 | // Mux down to FB_DW (if needed) 254 | if (IMUX_BITS > 0) 255 | assign fb_do = mem_do_mux[FB_DW*fb_addr_r[IMUX_BITS-1:0]+:FB_DW]; 256 | else 257 | assign fb_do = mem_do_mux; 258 | endgenerate 259 | 260 | // Map fb_di -> mem_di 261 | generate 262 | for (i=0; i<(1< 0) 270 | for (i=0; i 7 | * SPDX-License-Identifier: CERN-OHL-W-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module hub75_top #( 13 | parameter integer N_BANKS = 2, // # of parallel readout rows 14 | parameter integer N_ROWS = 32, // # of rows (must be power of 2!!!) 15 | parameter integer N_COLS = 64, // # of columns 16 | parameter integer N_CHANS = 3, // # of data channel 17 | parameter integer N_PLANES = 8, // # bitplanes 18 | parameter integer BITDEPTH = 24, // # bits per color 19 | parameter integer PHY_N = 1, // # of PHY in // 20 | parameter integer PHY_DDR = 0, // PHY DDR data output 21 | parameter integer PHY_AIR = 0, // PHY Address Inc/Reset 22 | 23 | parameter PANEL_INIT = "NONE", // 'NONE' or 'FM6126' 24 | parameter SCAN_MODE = "ZIGZAG", // 'LINEAR' or 'ZIGZAG' 25 | 26 | // Auto-set 27 | parameter integer SDW = N_BANKS * N_CHANS, 28 | parameter integer ESDW = SDW / (PHY_DDR ? 2 : 1), 29 | parameter integer LOG_N_BANKS = $clog2(N_BANKS), 30 | parameter integer LOG_N_ROWS = $clog2(N_ROWS), 31 | parameter integer LOG_N_COLS = $clog2(N_COLS) 32 | )( 33 | // Hub75 interface pads 34 | output wire [PHY_N-1:0] hub75_addr_inc, 35 | output wire [PHY_N-1:0] hub75_addr_rst, 36 | output wire [(PHY_N*LOG_N_ROWS)-1:0] hub75_addr, 37 | output wire [ESDW-1 :0] hub75_data, 38 | output wire [PHY_N-1:0] hub75_clk, 39 | output wire [PHY_N-1:0] hub75_le, 40 | output wire [PHY_N-1:0] hub75_blank, 41 | 42 | // Frame Buffer write interface 43 | // Row store/swap 44 | input wire [LOG_N_BANKS-1:0] fbw_bank_addr, 45 | input wire [LOG_N_ROWS-1:0] fbw_row_addr, 46 | input wire fbw_row_store, 47 | output wire fbw_row_rdy, 48 | input wire fbw_row_swap, 49 | 50 | // Line buffer access 51 | input wire [BITDEPTH-1:0] fbw_data, 52 | input wire [LOG_N_COLS-1:0] fbw_col_addr, 53 | input wire fbw_wren, 54 | 55 | // Frame buffer swap 56 | input wire frame_swap, 57 | output wire frame_rdy, 58 | 59 | // Control / Config 60 | input wire ctrl_run, 61 | 62 | input wire [7:0] cfg_pre_latch_len, 63 | input wire [7:0] cfg_latch_len, 64 | input wire [7:0] cfg_post_latch_len, 65 | input wire [7:0] cfg_bcm_bit_len, 66 | 67 | // Clock / Reset 68 | input wire clk, 69 | input wire clk_2x, 70 | input wire rst 71 | ); 72 | 73 | // Signals 74 | // ------- 75 | 76 | // Frame swap logic 77 | reg frame_swap_pending; 78 | wire frame_swap_fb; 79 | 80 | // PHY interface 81 | wire phy_addr_inc; 82 | wire phy_addr_rst; 83 | wire [LOG_N_ROWS-1:0] phy_addr; 84 | wire [SDW-1:0] phy_data; 85 | wire phy_clk; 86 | wire phy_le; 87 | wire phy_blank; 88 | 89 | wire phz_addr_inc; 90 | wire phz_addr_rst; 91 | wire [LOG_N_ROWS-1:0] phz_addr; 92 | wire [SDW-1:0] phz_data; 93 | wire phz_clk; 94 | wire phz_le; 95 | wire phz_blank; 96 | 97 | // Frame Buffer access 98 | // Read - Back Buffer loading 99 | wire [LOG_N_ROWS-1:0] fbr_row_addr; 100 | wire fbr_row_load; 101 | wire fbr_row_rdy; 102 | wire fbr_row_swap; 103 | 104 | // Read - Front Buffer access 105 | wire [(N_BANKS*N_CHANS*N_PLANES)-1:0] fbr_data; 106 | wire [LOG_N_COLS-1:0] fbr_col_addr; 107 | wire fbr_rden; 108 | 109 | // Scanning 110 | wire scan_go; 111 | wire scan_rdy; 112 | 113 | // Binary Code Modulator 114 | wire [LOG_N_ROWS-1:0] bcm_row; 115 | wire bcm_row_first; 116 | wire bcm_go; 117 | wire bcm_rdy, bcm_rdz; 118 | 119 | // Shifter 120 | wire [N_PLANES-1:0] shift_plane; 121 | wire shift_go; 122 | wire shift_rdy; 123 | 124 | // Blanking control 125 | wire [N_PLANES-1:0] blank_plane; 126 | wire blank_go; 127 | wire blank_rdy; 128 | 129 | 130 | // Sub-blocks 131 | // ---------- 132 | 133 | // Synchronized frame swap logic 134 | always @(posedge clk or posedge rst) 135 | if (rst) 136 | frame_swap_pending <= 1'b0; 137 | else 138 | frame_swap_pending <= (frame_swap_pending & ~scan_rdy) | frame_swap; 139 | 140 | assign frame_rdy = ~frame_swap_pending; 141 | assign scan_go = scan_rdy & ~frame_swap_pending & ctrl_run; 142 | assign frame_swap_fb = frame_swap_pending & scan_rdy; 143 | 144 | // The signal direction usage legend to the right of the modules has the 145 | // following structure: 146 | // * Signal direction -> (output from the module) 147 | // * Signal direction <- (input to the module) 148 | // * top: signal is connected to top and exposed to the world 149 | // * pad: signal is a gpio pad id the direction indicates if the pad is an 150 | // input (<-), output (->) or bidir (<->) 151 | // * local: signal is conneted to some local module logic 152 | // * hub75_*: signal is connected to the module hub75_* 153 | 154 | // Frame Buffer 155 | hub75_framebuffer #( 156 | .N_BANKS(N_BANKS), 157 | .N_ROWS(N_ROWS), 158 | .N_COLS(N_COLS), 159 | .N_CHANS(N_CHANS), 160 | .N_PLANES(N_PLANES), 161 | .BITDEPTH(BITDEPTH) 162 | ) fb_I ( 163 | .wr_bank_addr(fbw_bank_addr), // <- top 164 | .wr_row_addr(fbw_row_addr), // <- top 165 | .wr_row_store(fbw_row_store), // <- top 166 | .wr_row_rdy(fbw_row_rdy), // -> top 167 | .wr_row_swap(fbw_row_swap), // <- top 168 | .wr_data(fbw_data), // <- top 169 | .wr_col_addr(fbw_col_addr), // <- top 170 | .wr_en(fbw_wren), // <- top 171 | .rd_row_addr(fbr_row_addr), // <- hub75_scan 172 | .rd_row_load(fbr_row_load), // <- hub75_scan 173 | .rd_row_rdy(fbr_row_rdy), // -> hub75_scan 174 | .rd_row_swap(fbr_row_swap), // <- hub75_scan 175 | .rd_data(fbr_data), // -> hub75_shift 176 | .rd_col_addr(fbr_col_addr), // <- hub75_shift 177 | .rd_en(fbr_rden), // <- hub75_shift 178 | .frame_swap(frame_swap_fb), // <- local 179 | .clk(clk), // <- top 180 | .rst(rst) // <- top 181 | ); 182 | 183 | // Scan 184 | hub75_scan #( 185 | .N_ROWS(N_ROWS), 186 | .SCAN_MODE(SCAN_MODE) 187 | ) scan_I ( 188 | .bcm_row(bcm_row), // -> hub75_bcm 189 | .bcm_row_first(bcm_row_first), // -> hub75_bcm 190 | .bcm_go(bcm_go), // -> hub75_bcm 191 | .bcm_rdy(bcm_rdz), // <- hub75_bcm 192 | .fb_row_addr(fbr_row_addr), // -> hub75_framebuffer 193 | .fb_row_load(fbr_row_load), // -> hub75_framebuffer 194 | .fb_row_rdy(fbr_row_rdy), // <- hub75_framebuffer 195 | .fb_row_swap(fbr_row_swap), // -> hub75_framebuffer 196 | .ctrl_go(scan_go), // <- local 197 | .ctrl_rdy(scan_rdy), // -> local 198 | .clk(clk), // <- top 199 | .rst(rst) // <- top 200 | ); 201 | 202 | // Binary Code Modulator control 203 | hub75_bcm #( 204 | .N_PLANES(N_PLANES) 205 | ) bcm_I ( 206 | .phy_addr_inc(phy_addr_inc), // -> hub75_phy 207 | .phy_addr_rst(phy_addr_rst), // -> hub75_phy 208 | .phy_addr(phy_addr), // -> hub75_phy 209 | .phy_le(phy_le), // -> hub75_phy 210 | .shift_plane(shift_plane), // -> hub75_shift 211 | .shift_go(shift_go), // -> hub75_shift 212 | .shift_rdy(shift_rdy), // <- hub75_shift 213 | .blank_plane(blank_plane), // -> hub75_blanking 214 | .blank_go(blank_go), // -> hub75_blanking 215 | .blank_rdy(blank_rdy), // <- hub75_blanking 216 | .ctrl_row(bcm_row), // <- hub75_scan 217 | .ctrl_row_first(bcm_row_first), // <- hub75_scan 218 | .ctrl_go(bcm_go), // <- hub75_scan 219 | .ctrl_rdy(bcm_rdy), // -> hub75_scan 220 | .cfg_pre_latch_len(cfg_pre_latch_len), // <- top 221 | .cfg_latch_len(cfg_latch_len), // <- top 222 | .cfg_post_latch_len(cfg_post_latch_len), // <- top 223 | .clk(clk), // <- top 224 | .rst(rst) // <- top 225 | ); 226 | 227 | // Shifter 228 | hub75_shift #( 229 | .N_BANKS(N_BANKS), 230 | .N_COLS(N_COLS), 231 | .N_CHANS(N_CHANS), 232 | .N_PLANES(N_PLANES) 233 | ) shift_I ( 234 | .phy_data(phy_data), // -> hub75_phy 235 | .phy_clk(phy_clk), // -> hub75_phy 236 | .ram_data(fbr_data), // <- hub75_framebuffer 237 | .ram_col_addr(fbr_col_addr), // -> hub75_framebuffer 238 | .ram_rden(fbr_rden), // -> hub75_framebuffer 239 | .ctrl_plane(shift_plane), // <- hub75_bcm 240 | .ctrl_go(shift_go), // <- hub75_bcm 241 | .ctrl_rdy(shift_rdy), // -> hub75_bcm 242 | .clk(clk), // <- top 243 | .rst(rst) // <- top 244 | ); 245 | 246 | // Blanking control 247 | hub75_blanking #( 248 | .N_PLANES(N_PLANES) 249 | ) blank_I ( 250 | .phy_blank(phy_blank), // -> hub75_phy 251 | .ctrl_plane(blank_plane), // <- hub75_bcm 252 | .ctrl_go(blank_go), // <- hub75_bcm 253 | .ctrl_rdy(blank_rdy), // -> hub75_bcm 254 | .cfg_bcm_bit_len(cfg_bcm_bit_len), // <- top 255 | .clk(clk), // <- top 256 | .rst(rst) // <- top 257 | ); 258 | 259 | // Init injector 260 | generate 261 | if (PANEL_INIT == "NONE") begin 262 | 263 | // Direct PHY connection 264 | assign phz_addr_inc = phy_addr_inc; 265 | assign phz_addr_rst = phy_addr_rst; 266 | assign phz_addr = phy_addr; 267 | assign phz_data = phy_data; 268 | assign phz_clk = phy_clk; 269 | assign phz_le = phy_le; 270 | assign phz_blank = phy_blank; 271 | 272 | // No gating 273 | assign bcm_rdz = bcm_rdy; 274 | 275 | end else begin 276 | 277 | hub75_init_inject #( 278 | .N_BANKS(N_BANKS), 279 | .N_ROWS(N_ROWS), 280 | .N_COLS(N_COLS), 281 | .N_CHANS(N_CHANS) 282 | ) init_I ( 283 | .phy_in_addr_inc(phy_addr_inc), 284 | .phy_in_addr_rst(phy_addr_rst), 285 | .phy_in_addr(phy_addr), 286 | .phy_in_data(phy_data), 287 | .phy_in_clk(phy_clk), 288 | .phy_in_le(phy_le), 289 | .phy_in_blank(phy_blank), 290 | .phy_out_addr_inc(phz_addr_inc), 291 | .phy_out_addr_rst(phz_addr_rst), 292 | .phy_out_addr(phz_addr), 293 | .phy_out_data(phz_data), 294 | .phy_out_clk(phz_clk), 295 | .phy_out_le(phz_le), 296 | .phy_out_blank(phz_blank), 297 | .init_req(1'b1), 298 | .scan_go_in(scan_go), 299 | .bcm_rdy_in(bcm_rdy), 300 | .bcm_rdy_out(bcm_rdz), 301 | .shift_rdy_in(shift_rdy), 302 | .blank_rdy_in(blank_rdy), 303 | .clk(clk), 304 | .rst(rst) 305 | ); 306 | 307 | end 308 | endgenerate 309 | 310 | // Physical layer control 311 | generate 312 | if (PHY_DDR == 0) 313 | hub75_phy #( 314 | .N_BANKS(N_BANKS), 315 | .N_ROWS(N_ROWS), 316 | .N_CHANS(N_CHANS), 317 | .PHY_N(PHY_N), 318 | .PHY_AIR(PHY_AIR) 319 | ) phy_I ( 320 | .hub75_addr_inc(hub75_addr_inc),// -> pad 321 | .hub75_addr_rst(hub75_addr_rst),// -> pad 322 | .hub75_addr(hub75_addr), // -> pad 323 | .hub75_data(hub75_data), // -> pad 324 | .hub75_clk(hub75_clk), // -> pad 325 | .hub75_le(hub75_le), // -> pad 326 | .hub75_blank(hub75_blank), // -> pad 327 | .phy_addr_inc(phz_addr_inc), // <- hub75_bcm 328 | .phy_addr_rst(phz_addr_rst), // <- hub75_bcm 329 | .phy_addr(phz_addr), // <- hub75_bcm 330 | .phy_data(phz_data), // <- hub75_shift 331 | .phy_clk(phz_clk), // <- hub75_shift 332 | .phy_le(phz_le), // <- hub75_bcm 333 | .phy_blank(phz_blank), // <- hub75_blanking 334 | .clk(clk), // <- top 335 | .rst(rst) // <- top 336 | ); 337 | else 338 | hub75_phy_ddr #( 339 | .N_BANKS(N_BANKS), 340 | .N_ROWS(N_ROWS), 341 | .N_CHANS(N_CHANS), 342 | .PHY_N(PHY_N), 343 | .PHY_DDR(PHY_DDR), 344 | .PHY_AIR(PHY_AIR) 345 | ) phy_I ( 346 | .hub75_addr_inc(hub75_addr_inc),// -> pad 347 | .hub75_addr_rst(hub75_addr_rst),// -> pad 348 | .hub75_addr(hub75_addr), // -> pad 349 | .hub75_data(hub75_data), // -> pad 350 | .hub75_clk(hub75_clk), // -> pad 351 | .hub75_le(hub75_le), // -> pad 352 | .hub75_blank(hub75_blank), // -> pad 353 | .phy_addr_inc(phz_addr_inc), // <- hub75_bcm 354 | .phy_addr_rst(phz_addr_rst), // <- hub75_bcm 355 | .phy_addr(phz_addr), // <- hub75_bcm 356 | .phy_data(phz_data), // <- hub75_shift 357 | .phy_clk(phz_clk), // <- hub75_shift 358 | .phy_le(phz_le), // <- hub75_bcm 359 | .phy_blank(phz_blank), // <- hub75_blanking 360 | .clk(clk), // <- top 361 | .clk_2x(clk_2x), // <- top 362 | .rst(rst) // <- top 363 | ); 364 | endgenerate 365 | 366 | endmodule // hub75_top 367 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CERN Open Hardware Licence Version 2 - Weakly Reciprocal 2 | 3 | 4 | Preamble 5 | 6 | CERN has developed this licence to promote collaboration among 7 | hardware designers and to provide a legal tool which supports the 8 | freedom to use, study, modify, share and distribute hardware designs 9 | and products based on those designs. Version 2 of the CERN Open 10 | Hardware Licence comes in three variants: CERN-OHL-P (permissive); and 11 | two reciprocal licences: this licence, CERN-OHL-W (weakly reciprocal) 12 | and CERN-OHL-S (strongly reciprocal). 13 | 14 | The CERN-OHL-W is copyright CERN 2020. Anyone is welcome to use it, in 15 | unmodified form only. 16 | 17 | Use of this Licence does not imply any endorsement by CERN of any 18 | Licensor or their designs nor does it imply any involvement by CERN in 19 | their development. 20 | 21 | 22 | 1 Definitions 23 | 24 | 1.1 'Licence' means this CERN-OHL-W. 25 | 26 | 1.2 'Compatible Licence' means 27 | 28 | a) any earlier version of the CERN Open Hardware licence, or 29 | 30 | b) any version of the CERN-OHL-S or the CERN-OHL-W, or 31 | 32 | c) any licence which permits You to treat the Source to which 33 | it applies as licensed under CERN-OHL-S or CERN-OHL-W 34 | provided that on Conveyance of any such Source, or any 35 | associated Product You treat the Source in question as being 36 | licensed under CERN-OHL-S or CERN-OHL-W as appropriate. 37 | 38 | 1.3 'Source' means information such as design materials or digital 39 | code which can be applied to Make or test a Product or to 40 | prepare a Product for use, Conveyance or sale, regardless of its 41 | medium or how it is expressed. It may include Notices. 42 | 43 | 1.4 'Covered Source' means Source that is explicitly made available 44 | under this Licence. 45 | 46 | 1.5 'Product' means any device, component, work or physical object, 47 | whether in finished or intermediate form, arising from the use, 48 | application or processing of Covered Source. 49 | 50 | 1.6 'Make' means to create or configure something, whether by 51 | manufacture, assembly, compiling, loading or applying Covered 52 | Source or another Product or otherwise. 53 | 54 | 1.7 'Available Component' means any part, sub-assembly, library or 55 | code which: 56 | 57 | a) is licensed to You as Complete Source under a Compatible 58 | Licence; or 59 | 60 | b) is available, at the time a Product or the Source containing 61 | it is first Conveyed, to You and any other prospective 62 | licensees 63 | 64 | i) with sufficient rights and information (including any 65 | configuration and programming files and information 66 | about its characteristics and interfaces) to enable it 67 | either to be Made itself, or to be sourced and used to 68 | Make the Product; or 69 | ii) as part of the normal distribution of a tool used to 70 | design or Make the Product. 71 | 72 | 1.8 'External Material' means anything (including Source) which: 73 | 74 | a) is only combined with Covered Source in such a way that it 75 | interfaces with the Covered Source using a documented 76 | interface which is described in the Covered Source; and 77 | 78 | b) is not a derivative of or contains Covered Source, or, if it 79 | is, it is solely to the extent necessary to facilitate such 80 | interfacing. 81 | 82 | 1.9 'Complete Source' means the set of all Source necessary to Make 83 | a Product, in the preferred form for making modifications, 84 | including necessary installation and interfacing information 85 | both for the Product, and for any included Available Components. 86 | If the format is proprietary, it must also be made available in 87 | a format (if the proprietary tool can create it) which is 88 | viewable with a tool available to potential licensees and 89 | licensed under a licence approved by the Free Software 90 | Foundation or the Open Source Initiative. Complete Source need 91 | not include the Source of any Available Component, provided that 92 | You include in the Complete Source sufficient information to 93 | enable a recipient to Make or source and use the Available 94 | Component to Make the Product. 95 | 96 | 1.10 'Source Location' means a location where a Licensor has placed 97 | Covered Source, and which that Licensor reasonably believes will 98 | remain easily accessible for at least three years for anyone to 99 | obtain a digital copy. 100 | 101 | 1.11 'Notice' means copyright, acknowledgement and trademark notices, 102 | Source Location references, modification notices (subsection 103 | 3.3(b)) and all notices that refer to this Licence and to the 104 | disclaimer of warranties that are included in the Covered 105 | Source. 106 | 107 | 1.12 'Licensee' or 'You' means any person exercising rights under 108 | this Licence. 109 | 110 | 1.13 'Licensor' means a natural or legal person who creates or 111 | modifies Covered Source. A person may be a Licensee and a 112 | Licensor at the same time. 113 | 114 | 1.14 'Convey' means to communicate to the public or distribute. 115 | 116 | 117 | 2 Applicability 118 | 119 | 2.1 This Licence governs the use, copying, modification, Conveying 120 | of Covered Source and Products, and the Making of Products. By 121 | exercising any right granted under this Licence, You irrevocably 122 | accept these terms and conditions. 123 | 124 | 2.2 This Licence is granted by the Licensor directly to You, and 125 | shall apply worldwide and without limitation in time. 126 | 127 | 2.3 You shall not attempt to restrict by contract or otherwise the 128 | rights granted under this Licence to other Licensees. 129 | 130 | 2.4 This Licence is not intended to restrict fair use, fair dealing, 131 | or any other similar right. 132 | 133 | 134 | 3 Copying, Modifying and Conveying Covered Source 135 | 136 | 3.1 You may copy and Convey verbatim copies of Covered Source, in 137 | any medium, provided You retain all Notices. 138 | 139 | 3.2 You may modify Covered Source, other than Notices, provided that 140 | You irrevocably undertake to make that modified Covered Source 141 | available from a Source Location should You Convey a Product in 142 | circumstances where the recipient does not otherwise receive a 143 | copy of the modified Covered Source. In each case subsection 3.3 144 | shall apply. 145 | 146 | You may only delete Notices if they are no longer applicable to 147 | the corresponding Covered Source as modified by You and You may 148 | add additional Notices applicable to Your modifications. 149 | 150 | 3.3 You may Convey modified Covered Source (with the effect that You 151 | shall also become a Licensor) provided that You: 152 | 153 | a) retain Notices as required in subsection 3.2; 154 | 155 | b) add a Notice to the modified Covered Source stating that You 156 | have modified it, with the date and brief description of how 157 | You have modified it; 158 | 159 | c) add a Source Location Notice for the modified Covered Source 160 | if You Convey in circumstances where the recipient does not 161 | otherwise receive a copy of the modified Covered Source; and 162 | 163 | d) license the modified Covered Source under the terms and 164 | conditions of this Licence (or, as set out in subsection 165 | 8.3, a later version, if permitted by the licence of the 166 | original Covered Source). Such modified Covered Source must 167 | be licensed as a whole, but excluding Available Components 168 | contained in it or External Material to which it is 169 | interfaced, which remain licensed under their own applicable 170 | licences. 171 | 172 | 173 | 4 Making and Conveying Products 174 | 175 | 4.1 You may Make Products, and/or Convey them, provided that You 176 | either provide each recipient with a copy of the Complete Source 177 | or ensure that each recipient is notified of the Source Location 178 | of the Complete Source. That Complete Source includes Covered 179 | Source and You must accordingly satisfy Your obligations set out 180 | in subsection 3.3. If specified in a Notice, the Product must 181 | visibly and securely display the Source Location on it or its 182 | packaging or documentation in the manner specified in that 183 | Notice. 184 | 185 | 4.2 Where You Convey a Product which incorporates External Material, 186 | the Complete Source for that Product which You are required to 187 | provide under subsection 4.1 need not include any Source for the 188 | External Material. 189 | 190 | 4.3 You may license Products under terms of Your choice, provided 191 | that such terms do not restrict or attempt to restrict any 192 | recipients' rights under this Licence to the Covered Source. 193 | 194 | 195 | 5 Research and Development 196 | 197 | You may Convey Covered Source, modified Covered Source or Products to 198 | a legal entity carrying out development, testing or quality assurance 199 | work on Your behalf provided that the work is performed on terms which 200 | prevent the entity from both using the Source or Products for its own 201 | internal purposes and Conveying the Source or Products or any 202 | modifications to them to any person other than You. Any modifications 203 | made by the entity shall be deemed to be made by You pursuant to 204 | subsection 3.2. 205 | 206 | 207 | 6 DISCLAIMER AND LIABILITY 208 | 209 | 6.1 DISCLAIMER OF WARRANTY -- The Covered Source and any Products 210 | are provided 'as is' and any express or implied warranties, 211 | including, but not limited to, implied warranties of 212 | merchantability, of satisfactory quality, non-infringement of 213 | third party rights, and fitness for a particular purpose or use 214 | are disclaimed in respect of any Source or Product to the 215 | maximum extent permitted by law. The Licensor makes no 216 | representation that any Source or Product does not or will not 217 | infringe any patent, copyright, trade secret or other 218 | proprietary right. The entire risk as to the use, quality, and 219 | performance of any Source or Product shall be with You and not 220 | the Licensor. This disclaimer of warranty is an essential part 221 | of this Licence and a condition for the grant of any rights 222 | granted under this Licence. 223 | 224 | 6.2 EXCLUSION AND LIMITATION OF LIABILITY -- The Licensor shall, to 225 | the maximum extent permitted by law, have no liability for 226 | direct, indirect, special, incidental, consequential, exemplary, 227 | punitive or other damages of any character including, without 228 | limitation, procurement of substitute goods or services, loss of 229 | use, data or profits, or business interruption, however caused 230 | and on any theory of contract, warranty, tort (including 231 | negligence), product liability or otherwise, arising in any way 232 | in relation to the Covered Source, modified Covered Source 233 | and/or the Making or Conveyance of a Product, even if advised of 234 | the possibility of such damages, and You shall hold the 235 | Licensor(s) free and harmless from any liability, costs, 236 | damages, fees and expenses, including claims by third parties, 237 | in relation to such use. 238 | 239 | 240 | 7 Patents 241 | 242 | 7.1 Subject to the terms and conditions of this Licence, each 243 | Licensor hereby grants to You a perpetual, worldwide, 244 | non-exclusive, no-charge, royalty-free, irrevocable (except as 245 | stated in subsections 7.2 and 8.4) patent license to Make, have 246 | Made, use, offer to sell, sell, import, and otherwise transfer 247 | the Covered Source and Products, where such licence applies only 248 | to those patent claims licensable by such Licensor that are 249 | necessarily infringed by exercising rights under the Covered 250 | Source as Conveyed by that Licensor. 251 | 252 | 7.2 If You institute patent litigation against any entity (including 253 | a cross-claim or counterclaim in a lawsuit) alleging that the 254 | Covered Source or a Product constitutes direct or contributory 255 | patent infringement, or You seek any declaration that a patent 256 | licensed to You under this Licence is invalid or unenforceable 257 | then any rights granted to You under this Licence shall 258 | terminate as of the date such process is initiated. 259 | 260 | 261 | 8 General 262 | 263 | 8.1 If any provisions of this Licence are or subsequently become 264 | invalid or unenforceable for any reason, the remaining 265 | provisions shall remain effective. 266 | 267 | 8.2 You shall not use any of the name (including acronyms and 268 | abbreviations), image, or logo by which the Licensor or CERN is 269 | known, except where needed to comply with section 3, or where 270 | the use is otherwise allowed by law. Any such permitted use 271 | shall be factual and shall not be made so as to suggest any kind 272 | of endorsement or implication of involvement by the Licensor or 273 | its personnel. 274 | 275 | 8.3 CERN may publish updated versions and variants of this Licence 276 | which it considers to be in the spirit of this version, but may 277 | differ in detail to address new problems or concerns. New 278 | versions will be published with a unique version number and a 279 | variant identifier specifying the variant. If the Licensor has 280 | specified that a given variant applies to the Covered Source 281 | without specifying a version, You may treat that Covered Source 282 | as being released under any version of the CERN-OHL with that 283 | variant. If no variant is specified, the Covered Source shall be 284 | treated as being released under CERN-OHL-S. The Licensor may 285 | also specify that the Covered Source is subject to a specific 286 | version of the CERN-OHL or any later version in which case You 287 | may apply this or any later version of CERN-OHL with the same 288 | variant identifier published by CERN. 289 | 290 | You may treat Covered Source licensed under CERN-OHL-W as 291 | licensed under CERN-OHL-S if and only if all Available 292 | Components referenced in the Covered Source comply with the 293 | corresponding definition of Available Component for CERN-OHL-S. 294 | 295 | 8.4 This Licence shall terminate with immediate effect if You fail 296 | to comply with any of its terms and conditions. 297 | 298 | 8.5 However, if You cease all breaches of this Licence, then Your 299 | Licence from any Licensor is reinstated unless such Licensor has 300 | terminated this Licence by giving You, while You remain in 301 | breach, a notice specifying the breach and requiring You to cure 302 | it within 30 days, and You have failed to come into compliance 303 | in all material respects by the end of the 30 day period. Should 304 | You repeat the breach after receipt of a cure notice and 305 | subsequent reinstatement, this Licence will terminate 306 | immediately and permanently. Section 6 shall continue to apply 307 | after any termination. 308 | 309 | 8.6 This Licence shall not be enforceable except by a Licensor 310 | acting as such, and third party beneficiary rights are 311 | specifically excluded. 312 | --------------------------------------------------------------------------------