├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── doc ├── ice40_i2c_wb.md ├── ice40_rgb_wb.md └── ice40_spi_wb.md ├── no2core.mk ├── rtl ├── ice40_ebr.v ├── ice40_i2c_wb.v ├── ice40_iserdes.v ├── ice40_oserdes.v ├── ice40_rgb_wb.v ├── ice40_serdes_crg.v ├── ice40_serdes_dff.v ├── ice40_serdes_sync.v ├── ice40_spi_wb.v ├── ice40_spram_gen.v └── ice40_spram_wb.v ├── sim └── ice40_ebr_tb.v └── sw └── serdes-nextpnr-place.py /.gitignore: -------------------------------------------------------------------------------- 1 | build-tmp 2 | __pycache__ 3 | *.vcd 4 | .*.swp 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CERN Open Hardware Licence Version 2 - Permissive 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: this licence, CERN-OHL-P 11 | (permissive); and two reciprocal licences: CERN-OHL-W (weakly 12 | reciprocal) and CERN-OHL-S (strongly reciprocal). 13 | 14 | The CERN-OHL-P 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-P. 25 | 26 | 1.2 'Source' means information such as design materials or digital 27 | code which can be applied to Make or test a Product or to 28 | prepare a Product for use, Conveyance or sale, regardless of its 29 | medium or how it is expressed. It may include Notices. 30 | 31 | 1.3 'Covered Source' means Source that is explicitly made available 32 | under this Licence. 33 | 34 | 1.4 'Product' means any device, component, work or physical object, 35 | whether in finished or intermediate form, arising from the use, 36 | application or processing of Covered Source. 37 | 38 | 1.5 'Make' means to create or configure something, whether by 39 | manufacture, assembly, compiling, loading or applying Covered 40 | Source or another Product or otherwise. 41 | 42 | 1.6 'Notice' means copyright, acknowledgement and trademark notices, 43 | references to the location of any Notices, modification notices 44 | (subsection 3.3(b)) and all notices that refer to this Licence 45 | and to the disclaimer of warranties that are included in the 46 | Covered Source. 47 | 48 | 1.7 'Licensee' or 'You' means any person exercising rights under 49 | this Licence. 50 | 51 | 1.8 'Licensor' means a person who creates Source or modifies Covered 52 | Source and subsequently Conveys the resulting Covered Source 53 | under the terms and conditions of this Licence. A person may be 54 | a Licensee and a Licensor at the same time. 55 | 56 | 1.9 'Convey' means to communicate to the public or distribute. 57 | 58 | 59 | 2 Applicability 60 | 61 | 2.1 This Licence governs the use, copying, modification, Conveying 62 | of Covered Source and Products, and the Making of Products. By 63 | exercising any right granted under this Licence, You irrevocably 64 | accept these terms and conditions. 65 | 66 | 2.2 This Licence is granted by the Licensor directly to You, and 67 | shall apply worldwide and without limitation in time. 68 | 69 | 2.3 You shall not attempt to restrict by contract or otherwise the 70 | rights granted under this Licence to other Licensees. 71 | 72 | 2.4 This Licence is not intended to restrict fair use, fair dealing, 73 | or any other similar right. 74 | 75 | 76 | 3 Copying, Modifying and Conveying Covered Source 77 | 78 | 3.1 You may copy and Convey verbatim copies of Covered Source, in 79 | any medium, provided You retain all Notices. 80 | 81 | 3.2 You may modify Covered Source, other than Notices. 82 | 83 | You may only delete Notices if they are no longer applicable to 84 | the corresponding Covered Source as modified by You and You may 85 | add additional Notices applicable to Your modifications. 86 | 87 | 3.3 You may Convey modified Covered Source (with the effect that You 88 | shall also become a Licensor) provided that You: 89 | 90 | a) retain Notices as required in subsection 3.2; and 91 | 92 | b) add a Notice to the modified Covered Source stating that You 93 | have modified it, with the date and brief description of how 94 | You have modified it. 95 | 96 | 3.4 You may Convey Covered Source or modified Covered Source under 97 | licence terms which differ from the terms of this Licence 98 | provided that: 99 | 100 | a) You comply at all times with subsection 3.3; and 101 | 102 | b) You provide a copy of this Licence to anyone to whom You 103 | Convey Covered Source or modified Covered Source. 104 | 105 | 106 | 4 Making and Conveying Products 107 | 108 | You may Make Products, and/or Convey them, provided that You ensure 109 | that the recipient of the Product has access to any Notices applicable 110 | to the Product. 111 | 112 | 113 | 5 DISCLAIMER AND LIABILITY 114 | 115 | 5.1 DISCLAIMER OF WARRANTY -- The Covered Source and any Products 116 | are provided 'as is' and any express or implied warranties, 117 | including, but not limited to, implied warranties of 118 | merchantability, of satisfactory quality, non-infringement of 119 | third party rights, and fitness for a particular purpose or use 120 | are disclaimed in respect of any Source or Product to the 121 | maximum extent permitted by law. The Licensor makes no 122 | representation that any Source or Product does not or will not 123 | infringe any patent, copyright, trade secret or other 124 | proprietary right. The entire risk as to the use, quality, and 125 | performance of any Source or Product shall be with You and not 126 | the Licensor. This disclaimer of warranty is an essential part 127 | of this Licence and a condition for the grant of any rights 128 | granted under this Licence. 129 | 130 | 5.2 EXCLUSION AND LIMITATION OF LIABILITY -- The Licensor shall, to 131 | the maximum extent permitted by law, have no liability for 132 | direct, indirect, special, incidental, consequential, exemplary, 133 | punitive or other damages of any character including, without 134 | limitation, procurement of substitute goods or services, loss of 135 | use, data or profits, or business interruption, however caused 136 | and on any theory of contract, warranty, tort (including 137 | negligence), product liability or otherwise, arising in any way 138 | in relation to the Covered Source, modified Covered Source 139 | and/or the Making or Conveyance of a Product, even if advised of 140 | the possibility of such damages, and You shall hold the 141 | Licensor(s) free and harmless from any liability, costs, 142 | damages, fees and expenses, including claims by third parties, 143 | in relation to such use. 144 | 145 | 146 | 6 Patents 147 | 148 | 6.1 Subject to the terms and conditions of this Licence, each 149 | Licensor hereby grants to You a perpetual, worldwide, 150 | non-exclusive, no-charge, royalty-free, irrevocable (except as 151 | stated in this section 6, or where terminated by the Licensor 152 | for cause) patent license to Make, have Made, use, offer to 153 | sell, sell, import, and otherwise transfer the Covered Source 154 | and Products, where such licence applies only to those patent 155 | claims licensable by such Licensor that are necessarily 156 | infringed by exercising rights under the Covered Source as 157 | Conveyed by that Licensor. 158 | 159 | 6.2 If You institute patent litigation against any entity (including 160 | a cross-claim or counterclaim in a lawsuit) alleging that the 161 | Covered Source or a Product constitutes direct or contributory 162 | patent infringement, or You seek any declaration that a patent 163 | licensed to You under this Licence is invalid or unenforceable 164 | then any rights granted to You under this Licence shall 165 | terminate as of the date such process is initiated. 166 | 167 | 168 | 7 General 169 | 170 | 7.1 If any provisions of this Licence are or subsequently become 171 | invalid or unenforceable for any reason, the remaining 172 | provisions shall remain effective. 173 | 174 | 7.2 You shall not use any of the name (including acronyms and 175 | abbreviations), image, or logo by which the Licensor or CERN is 176 | known, except where needed to comply with section 3, or where 177 | the use is otherwise allowed by law. Any such permitted use 178 | shall be factual and shall not be made so as to suggest any kind 179 | of endorsement or implication of involvement by the Licensor or 180 | its personnel. 181 | 182 | 7.3 CERN may publish updated versions and variants of this Licence 183 | which it considers to be in the spirit of this version, but may 184 | differ in detail to address new problems or concerns. New 185 | versions will be published with a unique version number and a 186 | variant identifier specifying the variant. If the Licensor has 187 | specified that a given variant applies to the Covered Source 188 | without specifying a version, You may treat that Covered Source 189 | as being released under any version of the CERN-OHL with that 190 | variant. If no variant is specified, the Covered Source shall be 191 | treated as being released under CERN-OHL-S. The Licensor may 192 | also specify that the Covered Source is subject to a specific 193 | version of the CERN-OHL or any later version in which case You 194 | may apply this or any later version of CERN-OHL with the same 195 | variant identifier published by CERN. 196 | 197 | 7.4 This Licence shall not be enforceable except by a Licensor 198 | acting as such, and third party beneficiary rights are 199 | specifically excluded. 200 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CORE := ice40 2 | 3 | NO2BUILD_DIR ?= $(abspath ../../build) 4 | include $(NO2BUILD_DIR)/core-rules.mk 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nitro FPGA misc iCE40 specific cores 2 | ==================================== 3 | 4 | This contains a collection of utility cores specifically targeting the 5 | lattice iCE40 FPGA family. 6 | 7 | 8 | License 9 | ------- 10 | 11 | The cores in this repository are licensed under the 12 | "CERN Open Hardware Licence Version 2 - Permissive" license. 13 | 14 | See LICENSE file for full text. 15 | -------------------------------------------------------------------------------- /doc/ice40_i2c_wb.md: -------------------------------------------------------------------------------- 1 | `SB_I2C` wishbone wrapper 2 | ========================= 3 | 4 | Memory map 5 | ---------- 6 | 7 | ### `SB_I2C` registers ( Write Only, `0x00-0x3C` ) 8 | 9 | This wrapper directly maps each register of `SB_I2C` to a distinct wishbone 10 | word address. In this case for `SB_I2C` since the native bus is only 8 bits wide 11 | this means that each 32 bits word of the wishbone bus has the upper 24 bits unused. 12 | Also, all accesses have to be full width ( 32 bits ). 13 | 14 | Refer to Lattice TN1276 for the exact register description. 15 | 16 | Note that the upper 4 bits of the native bus address are automatically filled 17 | by the wrapper. 18 | -------------------------------------------------------------------------------- /doc/ice40_rgb_wb.md: -------------------------------------------------------------------------------- 1 | `SB_LEDDA_IP` & `SB_RGBA_DRV` wishbone wrapper 2 | ============================================== 3 | 4 | Memory map 5 | ---------- 6 | 7 | ### `SB_LEDDA_IP` registers ( Write Only, `0x00-0x3C` ) 8 | 9 | This wrapper directly maps each register of `SB_LEDDA_IP` to a distinct wishbone 10 | word address. In this case for `SB_LEDDA_IP` since the native bus is only 8 bits wide 11 | this means that each 32 bits word of the wishbone bus has the upper 24 bits unused. 12 | Also, all accesses have to be full width ( 32 bits ). 13 | 14 | Refer to Lattice TN1288 for the exact register description. 15 | 16 | ### Control word ( Write Only, `0x40` ) 17 | 18 | Single control word that controls a few control lines on the IP. 19 | 20 | ```text 21 | ,-----------------------------------------------------------------------------------------------, 22 | |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0| 23 | |-----------------------------------------------------------------------------------------------| 24 | | / |ce|le|ex| /| 25 | '-----------------------------------------------------------------------------------------------' 26 | 27 | * [3] - ce : SB_RGBA_DRV.CURREN 28 | * [2] - le : SB_RGBA_DRV.RGBLEDEN 29 | * [1] - ex : SB_LEDDA_IP.LEDDEXE 30 | ``` 31 | -------------------------------------------------------------------------------- /doc/ice40_spi_wb.md: -------------------------------------------------------------------------------- 1 | `SB_SPI` wishbone wrapper 2 | ========================= 3 | 4 | Memory map 5 | ---------- 6 | 7 | ### `SB_SPI` registers ( Write Only, `0x00-0x3C` ) 8 | 9 | This wrapper directly maps each register of `SB_SPI` to a distinct wishbone 10 | word address. In this case for `SB_SPI` since the native bus is only 8 bits wide 11 | this means that each 32 bits word of the wishbone bus has the upper 24 bits unused. 12 | Also, all accesses have to be full width ( 32 bits ). 13 | 14 | Refer to Lattice TN1276 for the exact register description. 15 | 16 | Note that the upper 4 bits of the native bus address are automatically filled 17 | by the wrapper. 18 | -------------------------------------------------------------------------------- /no2core.mk: -------------------------------------------------------------------------------- 1 | CORE := no2ice40 2 | 3 | RTL_SRCS_no2ice40 := $(addprefix rtl/, \ 4 | ice40_ebr.v \ 5 | ice40_i2c_wb.v \ 6 | ice40_rgb_wb.v \ 7 | ice40_spi_wb.v \ 8 | ice40_spram_gen.v \ 9 | ice40_spram_wb.v \ 10 | ice40_iserdes.v \ 11 | ice40_oserdes.v \ 12 | ice40_serdes_crg.v \ 13 | ice40_serdes_dff.v \ 14 | ice40_serdes_sync.v \ 15 | ) 16 | 17 | TESTBENCHES_no2ice40 := \ 18 | ice40_ebr_tb \ 19 | $(NULL) 20 | 21 | include $(NO2BUILD_DIR)/core-magic.mk 22 | -------------------------------------------------------------------------------- /rtl/ice40_ebr.v: -------------------------------------------------------------------------------- 1 | /* 2 | * ice40_ebr.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-P-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module ice40_ebr #( 13 | parameter integer READ_MODE = 0, /* 0 = 256x16, 1 = 512x8 */ 14 | parameter integer WRITE_MODE = 0, /* 2 = 1024x4, 3 = 2048x2 */ 15 | parameter integer MASK_WORKAROUND = 0, 16 | parameter integer NEG_WR_CLK = 0, 17 | parameter integer NEG_RD_CLK = 0, 18 | parameter INIT_FILE = "", 19 | 20 | // auto 21 | parameter integer WAW = 8 + WRITE_MODE, 22 | parameter integer WDW = 16 / (1 << WRITE_MODE), 23 | parameter integer RAW = 8 + READ_MODE, 24 | parameter integer RDW = 16 / (1 << READ_MODE) 25 | )( 26 | // Write 27 | input wire [WAW-1:0] wr_addr, 28 | input wire [WDW-1:0] wr_data, 29 | input wire [WDW-1:0] wr_mask, 30 | input wire wr_ena, 31 | input wire wr_clk, 32 | 33 | // Read 34 | input wire [RAW-1:0] rd_addr, 35 | output wire [RDW-1:0] rd_data, 36 | input wire rd_ena, 37 | input wire rd_clk 38 | ); 39 | 40 | genvar i; 41 | 42 | // Constants 43 | // --------- 44 | 45 | localparam integer WRITE_MODE_RAM = MASK_WORKAROUND ? 0 : WRITE_MODE; 46 | 47 | localparam integer RDO = (1 << READ_MODE) >> 2; 48 | localparam integer WDO = (1 << WRITE_MODE_RAM) >> 2; 49 | 50 | 51 | // Functions 52 | // --------- 53 | 54 | function [15:0] bitrev16 (input [15:0] sig); 55 | bitrev16 = { 56 | sig[15], sig[7], sig[11], sig[3], sig[13], sig[5], sig[9], sig[1], 57 | sig[14], sig[6], sig[10], sig[2], sig[12], sig[4], sig[8], sig[0] 58 | }; 59 | endfunction 60 | 61 | 62 | // Signals 63 | // ------- 64 | 65 | // Raw RAM 66 | wire [10:0] ram_wr_addr; 67 | wire [15:0] ram_wr_data; 68 | wire [15:0] ram_wr_mask; 69 | 70 | wire [10:0] ram_rd_addr; 71 | wire [15:0] ram_rd_data; 72 | 73 | 74 | // Read mapping 75 | // ------------ 76 | 77 | wire [15:0] rd_data_i; 78 | 79 | assign { ram_rd_addr[7:0], ram_rd_addr[8], ram_rd_addr[9], ram_rd_addr[10] } = { rd_addr, {(3-READ_MODE){1'b0}} }; 80 | 81 | assign rd_data_i = bitrev16({ {RDO{1'b0}}, ram_rd_data[15:RDO] }); 82 | assign rd_data = rd_data_i[RDW-1:0]; 83 | 84 | 85 | // Write mapping 86 | // ------------- 87 | 88 | generate 89 | if ((WRITE_MODE == 0) | (MASK_WORKAROUND == 0) ) begin 90 | // Normal Mapping rule 91 | wire [15:0] wr_data_i = bitrev16({ {(16-WDW){1'b0}}, wr_data }); 92 | wire [15:0] wr_mask_i = bitrev16({ {(16-WDW){1'b0}}, wr_mask }); 93 | 94 | assign ram_wr_data = { wr_data_i[15-WDO:0], {WDO{1'b0}} }; 95 | assign ram_wr_mask = { wr_mask_i[15-WDO:0], {WDO{1'b0}} }; 96 | assign { ram_wr_addr[7:0], ram_wr_addr[8], ram_wr_addr[9], ram_wr_addr[10] } = { wr_addr, {(3-WRITE_MODE){1'b0}} }; 97 | 98 | end else begin 99 | // We want mask support for non x16 mode 100 | // To do this we have to stay in x16 mode and manually handle the 101 | // write width adaptation 102 | wire [15:0] submask; 103 | 104 | assign ram_wr_data = bitrev16( {(1<> (4-WRITE_MODE)) == wr_addr[WRITE_MODE-1:0]); 110 | end 111 | endgenerate 112 | 113 | 114 | // Memory block 115 | // ------------ 116 | 117 | generate 118 | if ((NEG_RD_CLK == 0) && (NEG_WR_CLK == 0)) 119 | SB_RAM40_4K #( 120 | .INIT_FILE(INIT_FILE), 121 | .WRITE_MODE(WRITE_MODE_RAM), 122 | .READ_MODE(READ_MODE) 123 | ) ebr_I ( 124 | .RDATA(ram_rd_data), 125 | .RADDR(ram_rd_addr), 126 | .RCLK(rd_clk), 127 | .RCLKE(rd_ena), 128 | .RE(1'b1), 129 | .WDATA(ram_wr_data), 130 | .WADDR(ram_wr_addr), 131 | .MASK(ram_wr_mask), 132 | .WCLK(wr_clk), 133 | .WCLKE(wr_ena), 134 | .WE(1'b1) 135 | ); 136 | 137 | else if ((NEG_RD_CLK != 0) && (NEG_WR_CLK == 0)) 138 | SB_RAM40_4KNR #( 139 | .INIT_FILE(INIT_FILE), 140 | .WRITE_MODE(WRITE_MODE_RAM), 141 | .READ_MODE(READ_MODE) 142 | ) ebr_I ( 143 | .RDATA(ram_rd_data), 144 | .RADDR(ram_rd_addr), 145 | .RCLKN(rd_clk), 146 | .RCLKE(rd_ena), 147 | .RE(1'b1), 148 | .WDATA(ram_wr_data), 149 | .WADDR(ram_wr_addr), 150 | .MASK(ram_wr_mask), 151 | .WCLK(wr_clk), 152 | .WCLKE(wr_ena), 153 | .WE(1'b1) 154 | ); 155 | 156 | else if ((NEG_RD_CLK == 0) && (NEG_WR_CLK != 0)) 157 | SB_RAM40_4KNW #( 158 | .INIT_FILE(INIT_FILE), 159 | .WRITE_MODE(WRITE_MODE_RAM), 160 | .READ_MODE(READ_MODE) 161 | ) ebr_I ( 162 | .RDATA(ram_rd_data), 163 | .RADDR(ram_rd_addr), 164 | .RCLK(rd_clk), 165 | .RCLKE(rd_ena), 166 | .RE(1'b1), 167 | .WDATA(ram_wr_data), 168 | .WADDR(ram_wr_addr), 169 | .MASK(ram_wr_mask), 170 | .WCLKN(wr_clk), 171 | .WCLKE(wr_ena), 172 | .WE(1'b1) 173 | ); 174 | 175 | else if ((NEG_RD_CLK != 0) && (NEG_WR_CLK != 0)) 176 | SB_RAM40_4KNRNW #( 177 | .INIT_FILE(INIT_FILE), 178 | .WRITE_MODE(WRITE_MODE_RAM), 179 | .READ_MODE(READ_MODE) 180 | ) ebr_I ( 181 | .RDATA(ram_rd_data), 182 | .RADDR(ram_rd_addr), 183 | .RCLKN(rd_clk), 184 | .RCLKE(rd_ena), 185 | .RE(1'b1), 186 | .WDATA(ram_wr_data), 187 | .WADDR(ram_wr_addr), 188 | .MASK(ram_wr_mask), 189 | .WCLKN(wr_clk), 190 | .WCLKE(wr_ena), 191 | .WE(1'b1) 192 | ); 193 | 194 | endgenerate 195 | 196 | endmodule 197 | -------------------------------------------------------------------------------- /rtl/ice40_i2c_wb.v: -------------------------------------------------------------------------------- 1 | /* 2 | * ice40_i2c_wb.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Wishbone wrapper to use the SB_I2C hardmacro a bit more easily 7 | * 8 | * Copyright (C) 2020 Sylvain Munaut 9 | * SPDX-License-Identifier: CERN-OHL-P-2.0 10 | */ 11 | 12 | `default_nettype none 13 | 14 | module ice40_i2c_wb #( 15 | parameter integer WITH_IOB = 1, 16 | parameter integer UNIT = 0 // Unit 0 or 1 17 | )( 18 | // IO 19 | // IOBs included 20 | inout wire i2c_scl, 21 | inout wire i2c_sda, 22 | 23 | // Raw signals 24 | input wire i2c_scl_i, 25 | output wire i2c_scl_o, 26 | output wire i2c_scl_oe, 27 | 28 | input wire i2c_sda_i, 29 | output wire i2c_sda_o, 30 | output wire i2c_sda_oe, 31 | 32 | // Wishbone interface 33 | input wire [ 3:0] wb_addr, 34 | output wire [31:0] wb_rdata, 35 | input wire [31:0] wb_wdata, 36 | input wire wb_we, 37 | input wire wb_cyc, 38 | output wire wb_ack, 39 | 40 | // Aux signals 41 | output wire irq, 42 | output wire wakeup, 43 | 44 | // Common 45 | input wire clk, 46 | input wire rst 47 | ); 48 | 49 | // Signals 50 | // ------- 51 | 52 | wire [7:0] sb_addr; 53 | wire [7:0] sb_di; 54 | wire [7:0] sb_do; 55 | wire sb_rw; 56 | wire sb_stb; 57 | wire sb_ack; 58 | 59 | 60 | // Hard block 61 | // ---------- 62 | 63 | `ifndef SIM 64 | (* SCL_INPUT_FILTERED=1 *) 65 | SB_I2C #( 66 | .BUS_ADDR74(UNIT ? "0b0011" : "0b0001") 67 | ) i2c_I ( 68 | .SBCLKI (clk), 69 | .SBRWI (sb_rw), 70 | .SBSTBI (sb_stb), 71 | .SBADRI7 (sb_addr[7]), 72 | .SBADRI6 (sb_addr[6]), 73 | .SBADRI5 (sb_addr[5]), 74 | .SBADRI4 (sb_addr[4]), 75 | .SBADRI3 (sb_addr[3]), 76 | .SBADRI2 (sb_addr[2]), 77 | .SBADRI1 (sb_addr[1]), 78 | .SBADRI0 (sb_addr[0]), 79 | .SBDATI7 (sb_di[7]), 80 | .SBDATI6 (sb_di[6]), 81 | .SBDATI5 (sb_di[5]), 82 | .SBDATI4 (sb_di[4]), 83 | .SBDATI3 (sb_di[3]), 84 | .SBDATI2 (sb_di[2]), 85 | .SBDATI1 (sb_di[1]), 86 | .SBDATI0 (sb_di[0]), 87 | .SCLI (i2c_scl_i), 88 | .SDAI (i2c_sda_i), 89 | .SBDATO7 (sb_do[7]), 90 | .SBDATO6 (sb_do[6]), 91 | .SBDATO5 (sb_do[5]), 92 | .SBDATO4 (sb_do[4]), 93 | .SBDATO3 (sb_do[3]), 94 | .SBDATO2 (sb_do[2]), 95 | .SBDATO1 (sb_do[1]), 96 | .SBDATO0 (sb_do[0]), 97 | .SBACKO (sb_ack), 98 | .I2CIRQ (irq), 99 | .I2CWKUP (wakeup), 100 | .SCLO (i2c_scl_o), 101 | .SCLOE (i2c_scl_oe), 102 | .SDAO (i2c_sda_o), 103 | .SDAOE (i2c_sda_oe) 104 | ); 105 | `else 106 | assign sb_ack = sb_stb; 107 | assign sb_do = 8'h00; 108 | `endif 109 | 110 | 111 | // IOB (if needed) 112 | // --------------- 113 | 114 | generate 115 | if (WITH_IOB) begin 116 | 117 | SB_IO #( 118 | .PIN_TYPE(6'b101001), 119 | .PULLUP(1'b1) 120 | ) i2c_io_I[1:0] ( 121 | .PACKAGE_PIN ({i2c_scl, i2c_sda }), 122 | .OUTPUT_ENABLE({i2c_scl_oe, i2c_sda_oe}), 123 | .D_OUT_0 ({i2c_scl_o, i2c_sda_o }), 124 | .D_IN_0 ({i2c_scl_i, i2c_sda_i }) 125 | ); 126 | 127 | end 128 | endgenerate 129 | 130 | 131 | // Bus interface 132 | // ------------- 133 | 134 | assign sb_addr = { (UNIT ? 4'h3 : 4'h1), wb_addr }; 135 | assign sb_di = wb_wdata[7:0]; 136 | assign sb_rw = wb_we; 137 | assign sb_stb = wb_cyc; 138 | 139 | assign wb_rdata = { 24'h0000, wb_cyc ? sb_do : 8'h00 }; 140 | assign wb_ack = sb_ack; 141 | 142 | endmodule // ice40_i2c_wb 143 | -------------------------------------------------------------------------------- /rtl/ice40_iserdes.v: -------------------------------------------------------------------------------- 1 | /* 2 | * ice40_iserdes.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-P-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module ice40_iserdes #( 13 | parameter EDGE_SEL = "SINGLE_POS", // "SINGLE_POS" / "SINGLE_NEG" / "DUAL_POS" / "DUAL_POS_NEG" 14 | parameter PHASE_SEL = "STATIC", // "STATIC" / "DYNAMIC" 15 | parameter integer PHASE = 0, 16 | parameter integer SERDES_GRP = 0 17 | )( 18 | input wire [1:0] d, 19 | output wire [3:0] q, 20 | input wire edge_sel, 21 | input wire [1:0] phase_sel, 22 | input wire sync, 23 | input wire clk_1x, 24 | input wire clk_4x 25 | ); 26 | 27 | genvar i, j; 28 | 29 | /* 0 1 30 | * SINGLE_POS POS / 31 | * SINGLE_NEG NEG / 32 | * DUAL_POS POS / 33 | * DUAL_POS_NEG POS NEG 34 | */ 35 | 36 | // FIXME: The DUAL_POS_NEG mode would need a negative edge sync signal as 37 | // well 38 | 39 | 40 | // Signals 41 | // ------- 42 | 43 | wire [3:0] shift_in[0:1]; 44 | wire [3:0] shift_out[0:1]; 45 | 46 | wire [3:0] fcap_in[0:1]; 47 | wire [3:0] fcap_out[0:1]; 48 | 49 | 50 | // Fast paths 51 | // ---------- 52 | 53 | // - For the "SINGLE_{POS,NEG}", we only have a single path 54 | // - For the "DUAL_POS_POS" case it's a single path as well with a pre-sel 55 | // mux. If dynamic phase is also enabled, this option will have an added 56 | // delay (because need for mux between path and between phase) 57 | // - For the "DUAL_POS_NEG" case, we have two independent paths 58 | 59 | generate 60 | for (j=0; j<2; j=j+1) begin 61 | if ((j == 0) || (EDGE_SEL == "DUAL_POS_NEG")) 62 | begin : fp 63 | localparam IS_NEG = (EDGE_SEL == "SINGLE_NEG") || (j == 1); 64 | wire edge_active; 65 | wire din_mux; 66 | wire din; 67 | 68 | // Edge Select 69 | // ----------- 70 | 71 | assign edge_active = (EDGE_SEL != "DUAL_POS_NEG") || (edge_sel == j); 72 | 73 | if (EDGE_SEL == "DUAL_POS_POS") begin 74 | // Need a pre-mux 75 | (* dont_touch *) 76 | SB_LUT4 #( 77 | .LUT_INIT(16'hFC30) 78 | ) lut_edgemux_I ( 79 | .I0(1'b0), 80 | .I1(edge_sel), 81 | .I2(d[0]), 82 | .I3(d[1]), // Fast Path for the neg-edge 83 | .O(din_mux) 84 | ); 85 | 86 | if (PHASE_SEL == "DYNAMIC") 87 | // If we have dynamic phase, we need the added stage 88 | // for timing 89 | ice40_serdes_dff #( 90 | .NEG(IS_NEG), 91 | .SERDES_GRP( (SERDES_GRP << 8) | 'h4b0 | (j << 4) ) 92 | ) dff_edgemux_I ( 93 | .d(din_mux), 94 | .q(din), 95 | .c(clk_4x) 96 | ); 97 | else 98 | // This mux can be packed with the first shift 99 | // register stage 100 | assign din = din_mux; 101 | 102 | end else begin 103 | // Directly from IOB signal 104 | assign din = d[j]; 105 | end 106 | 107 | 108 | // Shifter 109 | // ------- 110 | 111 | assign shift_in[j] = { shift_out[j][2:0], din }; 112 | 113 | for (i=0; i<4; i=i+1) 114 | begin 115 | ice40_serdes_dff #( 116 | .NEG(IS_NEG), 117 | .SERDES_GRP( (SERDES_GRP << 8) | 'h4a0 | (j << 4) | i ) 118 | ) dff_shift_I ( 119 | .d(shift_in[j][i]), 120 | .q(shift_out[j][i]), 121 | .c(clk_4x) 122 | ); 123 | end 124 | 125 | 126 | // Fast Capture 127 | // ------------ 128 | 129 | // If we have dynamic phase selection, apply the LSB here 130 | if (PHASE_SEL == "DYNAMIC") 131 | assign fcap_in[j] = edge_active ? (phase_sel[0] ? shift_out[j] : shift_in[j]) : 4'h0; 132 | else 133 | assign fcap_in[j] = edge_active ? shift_out[j] : 4'h0; 134 | 135 | // Register 136 | for (i=0; i<4; i=i+1) 137 | begin 138 | ice40_serdes_dff #( 139 | .NEG(IS_NEG), 140 | .ENA(1), 141 | .SERDES_GRP( (SERDES_GRP << 8) | 'h490 | (j << 4) | i ) 142 | ) dff_shift_I ( 143 | .d(fcap_in[j][i]), 144 | .q(fcap_out[j][i]), 145 | .e(sync), 146 | .c(clk_4x) 147 | ); 148 | end 149 | end 150 | else 151 | begin 152 | // Dummy 153 | assign fcap_out[j] = 4'h0; 154 | end 155 | end 156 | endgenerate 157 | 158 | 159 | // Slow Capture 160 | // ------------ 161 | 162 | generate 163 | if (PHASE_SEL == "STATIC") 164 | begin 165 | // Static Phase 166 | // - - - - - - - 167 | 168 | wire [3+PHASE:0] scap_in; 169 | wire [3+PHASE:0] scap_out; 170 | 171 | // Input 172 | if (PHASE > 0) 173 | assign scap_in[3+PHASE:4] = scap_out[PHASE-1:0]; 174 | 175 | assign scap_in[3:0] = fcap_out[0] | fcap_out[1]; 176 | 177 | // Registers 178 | for (i=0; i<(4+PHASE); i=i+1) 179 | ice40_serdes_dff #( 180 | .SERDES_GRP( (SERDES_GRP << 8) | 'h680 | i ) 181 | ) dff_scap_I ( 182 | .d(scap_in[i]), 183 | .q(scap_out[i]), 184 | .c(clk_1x) 185 | ); 186 | 187 | // Output 188 | assign q = scap_out[3+PHASE:PHASE]; 189 | end 190 | else 191 | begin 192 | // Dynamic Phase 193 | // - - - - - - - 194 | 195 | wire [5:0] scap_in; 196 | wire [5:0] scap_out; 197 | 198 | // Input 199 | if (EDGE_SEL == "DUAL_POS_NEG") 200 | begin 201 | 202 | // Dual Edge Path 203 | // - - - - - - - - 204 | 205 | wire [1:0] scap_pre_or; 206 | 207 | // Pre-OR 208 | (* SERDES_GRP=( (SERDES_GRP << 8) | 'h680 | 6 ) *) 209 | (* dont_touch *) 210 | SB_LUT4 #( 211 | .LUT_INIT(16'hFFF0) 212 | ) or_lut_2_I ( 213 | .I0(1'b0), 214 | .I1(1'b0), 215 | .I2(fcap_out[1][2]), 216 | .I3(fcap_out[0][2]), 217 | .O(scap_pre_or[0]) 218 | ); 219 | 220 | (* SERDES_GRP=( (SERDES_GRP << 8) | 'h680 | 7 ) *) 221 | (* dont_touch *) 222 | SB_LUT4 #( 223 | .LUT_INIT(16'hFFF0) 224 | ) or_lut_3_I ( 225 | .I0(1'b0), 226 | .I1(1'b0), 227 | .I2(fcap_out[1][3]), 228 | .I3(fcap_out[0][3]), 229 | .O(scap_pre_or[1]) 230 | ); 231 | 232 | // Main muxes 233 | (* dont_touch *) 234 | SB_LUT4 #( 235 | .LUT_INIT(16'hFE54) 236 | ) mux_lut_I[3:0] ( 237 | .I0(phase_sel[1]), 238 | .I1(fcap_out[1][3:0]), 239 | .I2(fcap_out[0][3:0]), 240 | .I3({scap_out[5:4], scap_pre_or}), 241 | .O(scap_in[3:0]) 242 | ); 243 | 244 | // Save regs 245 | assign scap_in[5:4] = fcap_out[0][1:0] | fcap_out[1][1:0]; 246 | 247 | end 248 | else 249 | begin 250 | 251 | // Single Edge Path 252 | // - - - - - - - - - 253 | 254 | assign scap_in = { 255 | fcap_out[0][1:0], 256 | phase_sel[1] ? 257 | { scap_out[5:4], fcap_out[0][3:2] } : 258 | fcap_out[0][3:0] 259 | }; 260 | 261 | end 262 | 263 | // Registers 264 | for (i=0; i<6; i=i+1) 265 | ice40_serdes_dff #( 266 | .SERDES_GRP( (SERDES_GRP << 8) | 'h680 | i ) 267 | ) dff_scap_I ( 268 | .d(scap_in[i]), 269 | .q(scap_out[i]), 270 | .c(clk_1x) 271 | ); 272 | 273 | // Output 274 | assign q = scap_out[3:0]; 275 | end 276 | endgenerate 277 | 278 | endmodule 279 | -------------------------------------------------------------------------------- /rtl/ice40_oserdes.v: -------------------------------------------------------------------------------- 1 | /* 2 | * ice40_oserdes.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-P-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module ice40_oserdes #( 13 | parameter MODE = "DATA", // "DATA" / "CLK90_2X" / "CLK90_4X" 14 | parameter integer SERDES_GRP = 0 15 | )( 16 | input wire [3:0] d, 17 | output wire [1:0] q, 18 | input wire sync, 19 | input wire clk_1x, 20 | input wire clk_4x 21 | ); 22 | 23 | genvar i; 24 | 25 | 26 | // Signals 27 | // ------- 28 | 29 | wire [3:0] cap_in; 30 | wire [3:0] cap_out; 31 | 32 | wire [3:0] shift_in; 33 | wire [3:0] shift_out; 34 | 35 | wire delay_out; 36 | 37 | 38 | // Capture 39 | // ------- 40 | 41 | assign cap_in = (MODE == "CLK90_2X") ? { d[1], 1'b0, d[0], 1'b0 } : d; 42 | 43 | generate 44 | for (i=0; i<4; i=i+1) 45 | ice40_serdes_dff #( 46 | .SERDES_GRP( (SERDES_GRP << 8) | 'h00 | i ) 47 | ) dff_cap_I ( 48 | .d(cap_in[i]), 49 | .q(cap_out[i]), 50 | .c(clk_1x) 51 | ); 52 | endgenerate 53 | 54 | 55 | // Shifter 56 | // ------- 57 | 58 | assign shift_in = sync ? cap_out : { shift_out[2:0], 1'b0 }; 59 | 60 | generate 61 | for (i=0; i<4; i=i+1) 62 | ice40_serdes_dff #( 63 | .SERDES_GRP( (SERDES_GRP << 8) | 'h10 | i ) 64 | ) dff_shift_I ( 65 | .d(shift_in[i]), 66 | .q(shift_out[i]), 67 | .c(clk_4x) 68 | ); 69 | endgenerate 70 | 71 | 72 | // Output 73 | // ------ 74 | 75 | generate 76 | if ((MODE == "CLK90_2X") || (MODE == "CLK90_4X")) begin 77 | // Delay FF for falling edge 78 | ice40_serdes_dff #( 79 | .SERDES_GRP( (SERDES_GRP << 8) | 'h20 ) 80 | ) dff_out_I ( 81 | .d(shift_out[3]), 82 | .q(delay_out), 83 | .c(clk_4x) 84 | ); 85 | 86 | // Output depends on clock mode 87 | assign q[0] = (MODE == "CLK90_2X") ? delay_out : 1'b0; 88 | assign q[1] = delay_out; 89 | end else begin 90 | // Simple data map, fall edge output un-used 91 | assign q[0] = shift_out[3]; 92 | assign q[1] = 1'b0; 93 | end 94 | endgenerate 95 | 96 | endmodule 97 | -------------------------------------------------------------------------------- /rtl/ice40_rgb_wb.v: -------------------------------------------------------------------------------- 1 | /* 2 | * ice40_rgb_wb.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Wishbone wrapper to use the SB_LEDDA_IP & SB_RGBA_DRV hard macro 7 | * a bit more easily 8 | * 9 | * Copyright (C) 2020 Sylvain Munaut 10 | * SPDX-License-Identifier: CERN-OHL-P-2.0 11 | */ 12 | 13 | `default_nettype none 14 | 15 | module ice40_rgb_wb #( 16 | parameter CURRENT_MODE = "0b1", 17 | parameter RGB0_CURRENT = "0b000001", 18 | parameter RGB1_CURRENT = "0b000001", 19 | parameter RGB2_CURRENT = "0b000001" 20 | )( 21 | // RGB pad 22 | output wire [ 2:0] pad_rgb, 23 | 24 | // Wishbone interface 25 | input wire [ 4:0] wb_addr, 26 | output wire [31:0] wb_rdata, 27 | input wire [31:0] wb_wdata, 28 | input wire wb_we, 29 | input wire wb_cyc, 30 | output wire wb_ack, 31 | 32 | // Common 33 | input wire clk, 34 | input wire rst 35 | ); 36 | 37 | // Signals 38 | // ------- 39 | 40 | reg [4:0] led_ctrl; 41 | wire [2:0] pwm_rgb; 42 | 43 | 44 | // PWM IP 45 | // ------ 46 | 47 | SB_LEDDA_IP led_I ( 48 | .LEDDCS (wb_addr[4] & wb_we), 49 | .LEDDCLK (clk), 50 | .LEDDDAT7 (wb_wdata[7]), 51 | .LEDDDAT6 (wb_wdata[6]), 52 | .LEDDDAT5 (wb_wdata[5]), 53 | .LEDDDAT4 (wb_wdata[4]), 54 | .LEDDDAT3 (wb_wdata[3]), 55 | .LEDDDAT2 (wb_wdata[2]), 56 | .LEDDDAT1 (wb_wdata[1]), 57 | .LEDDDAT0 (wb_wdata[0]), 58 | .LEDDADDR3(wb_addr[3]), 59 | .LEDDADDR2(wb_addr[2]), 60 | .LEDDADDR1(wb_addr[1]), 61 | .LEDDADDR0(wb_addr[0]), 62 | .LEDDDEN (wb_cyc), 63 | .LEDDEXE (led_ctrl[1]), 64 | .PWMOUT0 (pwm_rgb[0]), 65 | .PWMOUT1 (pwm_rgb[1]), 66 | .PWMOUT2 (pwm_rgb[2]), 67 | .LEDDON () 68 | ); 69 | 70 | 71 | // CC Driver 72 | // --------- 73 | 74 | SB_RGBA_DRV #( 75 | .CURRENT_MODE(CURRENT_MODE), 76 | .RGB0_CURRENT(RGB0_CURRENT), 77 | .RGB1_CURRENT(RGB1_CURRENT), 78 | .RGB2_CURRENT(RGB2_CURRENT) 79 | ) rgb_drv_I ( 80 | .RGBLEDEN(led_ctrl[2]), 81 | .RGB0PWM (pwm_rgb[0]), 82 | .RGB1PWM (pwm_rgb[1]), 83 | .RGB2PWM (pwm_rgb[2]), 84 | .CURREN (led_ctrl[3]), 85 | .RGB0 (pad_rgb[0]), 86 | .RGB1 (pad_rgb[1]), 87 | .RGB2 (pad_rgb[2]) 88 | ); 89 | 90 | 91 | // Bus interface 92 | // ------------- 93 | 94 | always @(posedge clk or posedge rst) 95 | if (rst) 96 | led_ctrl <= 0; 97 | else if (wb_cyc & ~wb_addr[4] & wb_we) 98 | led_ctrl <= wb_wdata[4:0]; 99 | 100 | assign wb_rdata = 32'h00000000; 101 | assign wb_ack = wb_cyc; 102 | 103 | endmodule // ice40_rgb_wb 104 | -------------------------------------------------------------------------------- /rtl/ice40_serdes_crg.v: -------------------------------------------------------------------------------- 1 | /* 2 | * ice40_serdes_crg.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-P-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module ice40_serdes_crg #( 13 | parameter BEL_CLOCK_1X = "X12/Y0/gb", 14 | parameter BEL_CLOCK_2X = "X13/Y0/gb", 15 | parameter integer NO_CLOCK_2X = 0 16 | )( 17 | // Input from PLL 18 | input wire clk_4x, 19 | input wire pll_lock, 20 | 21 | // Outputs 22 | output wire clk_1x, 23 | output wire clk_2x, 24 | output wire rst 25 | ); 26 | 27 | // Signals 28 | // ------- 29 | 30 | // Reset 31 | reg [3:0] rst_cnt_nxt[0:15]; 32 | reg [3:0] rst_cnt = 4'h8; 33 | reg rst_i; 34 | 35 | // Clock Divider 36 | reg [1:0] clk_div; 37 | wire clk_sync_i; 38 | 39 | 40 | // Reset 41 | // ----- 42 | 43 | // Counter 44 | initial begin : rst_init 45 | integer i; 46 | for (i=0; i<16; i=i+1) 47 | rst_cnt_nxt[i] = i==15 ? i : (i+1); 48 | end 49 | 50 | always @(posedge clk_4x or negedge pll_lock) 51 | if (~pll_lock) 52 | rst_cnt <= 4'h0; 53 | else 54 | rst_cnt <= rst_cnt_nxt[rst_cnt]; 55 | 56 | // Final FF 57 | always @(posedge clk_4x or negedge pll_lock) 58 | if (~pll_lock) 59 | rst_i <= 1'b1; 60 | else 61 | rst_i <= (rst_cnt != 4'hf); 62 | 63 | // Buffer reset 64 | SB_GB gbuf_rst_I ( 65 | .USER_SIGNAL_TO_GLOBAL_BUFFER(rst_i), 66 | .GLOBAL_BUFFER_OUTPUT(rst) 67 | ); 68 | 69 | 70 | // Clock Divider & Sync 71 | // -------------------- 72 | 73 | // Simple counter to generate the edges 74 | always @(posedge clk_4x or negedge pll_lock) 75 | if (~pll_lock) 76 | clk_div <= 2'b00; 77 | else 78 | clk_div <= clk_div + rst_cnt[3]; 79 | 80 | // Buffer clk_2x 81 | generate 82 | if (NO_CLOCK_2X) 83 | assign clk_2x = 1'b0; 84 | else 85 | (* BEL=BEL_CLOCK_2X *) 86 | SB_GB gbuf_2x_I ( 87 | .USER_SIGNAL_TO_GLOBAL_BUFFER(clk_div[0]), 88 | .GLOBAL_BUFFER_OUTPUT(clk_2x) 89 | ); 90 | endgenerate 91 | 92 | // Buffer clk_1x 93 | (* BEL=BEL_CLOCK_1X *) 94 | SB_GB gbuf_1x_I ( 95 | .USER_SIGNAL_TO_GLOBAL_BUFFER(clk_div[1]), 96 | .GLOBAL_BUFFER_OUTPUT(clk_1x) 97 | ); 98 | 99 | endmodule 100 | -------------------------------------------------------------------------------- /rtl/ice40_serdes_dff.v: -------------------------------------------------------------------------------- 1 | /* 2 | * ice40_serdes_dff.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-P-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module ice40_serdes_dff #( 13 | parameter integer NEG = 0, 14 | parameter integer ENA = 0, 15 | parameter integer RST = 0, 16 | parameter integer SERDES_GRP = -1, 17 | parameter SERDES_ATTR = "", 18 | parameter BEL = "" 19 | )( 20 | input wire d, 21 | output wire q, 22 | input wire e, 23 | input wire r, 24 | input wire c 25 | ); 26 | parameter TYPE = (RST ? 4 : 0) | (ENA ? 2 : 0) | (NEG ? 1 : 0); 27 | 28 | generate 29 | if (TYPE == 0) // Simple 30 | (* BEL=BEL, SERDES_GRP=SERDES_GRP, SERDES_ATTR=SERDES_ATTR *) 31 | (* dont_touch, keep *) 32 | SB_DFF dff_I ( 33 | .D(d), 34 | .Q(q), 35 | .C(c) 36 | ); 37 | 38 | else if (TYPE == 1) // NEG 39 | (* BEL=BEL, SERDES_GRP=SERDES_GRP, SERDES_ATTR=SERDES_ATTR *) 40 | (* dont_touch, keep *) 41 | SB_DFFN dff_I ( 42 | .D(d), 43 | .Q(q), 44 | .C(c) 45 | ); 46 | 47 | else if (TYPE == 2) // ENA 48 | (* BEL=BEL, SERDES_GRP=SERDES_GRP, SERDES_ATTR=SERDES_ATTR *) 49 | (* dont_touch, keep *) 50 | SB_DFFE dff_I ( 51 | .D(d), 52 | .Q(q), 53 | .E(e), 54 | .C(c) 55 | ); 56 | 57 | else if (TYPE == 3) // NEG ENA 58 | (* BEL=BEL, SERDES_GRP=SERDES_GRP, SERDES_ATTR=SERDES_ATTR *) 59 | (* dont_touch, keep *) 60 | SB_DFFNE dff_I ( 61 | .D(d), 62 | .Q(q), 63 | .E(e), 64 | .C(c) 65 | ); 66 | 67 | else if (TYPE == 4) // RST 68 | (* BEL=BEL, SERDES_GRP=SERDES_GRP, SERDES_ATTR=SERDES_ATTR *) 69 | (* dont_touch, keep *) 70 | SB_DFFR dff_I ( 71 | .D(d), 72 | .Q(q), 73 | .R(r), 74 | .C(c) 75 | ); 76 | 77 | else if (TYPE == 5) // NEG RST 78 | (* BEL=BEL, SERDES_GRP=SERDES_GRP, SERDES_ATTR=SERDES_ATTR *) 79 | (* dont_touch, keep *) 80 | SB_DFFNR dff_I ( 81 | .D(d), 82 | .Q(q), 83 | .R(r), 84 | .C(c) 85 | ); 86 | 87 | else if (TYPE == 6) // ENA RST 88 | (* BEL=BEL, SERDES_GRP=SERDES_GRP, SERDES_ATTR=SERDES_ATTR *) 89 | (* dont_touch, keep *) 90 | SB_DFFER dff_I ( 91 | .D(d), 92 | .Q(q), 93 | .E(e), 94 | .R(r), 95 | .C(c) 96 | ); 97 | 98 | else if (TYPE == 7) // NEG ENA RST 99 | (* BEL=BEL, SERDES_GRP=SERDES_GRP, SERDES_ATTR=SERDES_ATTR *) 100 | (* dont_touch, keep *) 101 | SB_DFFNER dff_I ( 102 | .D(d), 103 | .Q(q), 104 | .E(e), 105 | .R(r), 106 | .C(c) 107 | ); 108 | 109 | endgenerate 110 | 111 | endmodule 112 | -------------------------------------------------------------------------------- /rtl/ice40_serdes_sync.v: -------------------------------------------------------------------------------- 1 | /* 2 | * ice40_serdes_sync.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-P-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module ice40_serdes_sync #( 13 | parameter integer PHASE = 0, 14 | parameter integer NEG_EDGE = 0, 15 | parameter integer GLOBAL_BUF = 0, 16 | parameter integer LOCAL_BUF = 0, 17 | parameter BEL_COL = "X12", 18 | parameter BEL_ROW = "Y15", // Ignored if using LOCAL_BUF, Y15 used in that case 19 | parameter BEL_GB = "" 20 | )( 21 | input wire clk_slow, 22 | input wire clk_fast, 23 | input wire rst, 24 | output wire sync 25 | ); 26 | localparam BEL_BASE = LOCAL_BUF ? { BEL_COL, "/Y15" } : { BEL_COL, "/", BEL_ROW }; 27 | localparam PHASE_CMP = LOCAL_BUF ? ((PHASE + 3) % 4) : PHASE; 28 | 29 | wire [1:0] clk_samp; 30 | wire [1:0] edge_det; 31 | wire [1:0] edge_found; 32 | wire [1:0] cnt_next; 33 | wire [1:0] cnt_val; 34 | 35 | wire [1:0] sync_next; 36 | wire [1:0] sync_i; 37 | 38 | // Double sample of the slow clock 39 | ice40_serdes_dff #( 40 | .NEG(NEG_EDGE), 41 | .RST(1), 42 | .BEL({BEL_BASE, "/lc7"}) 43 | ) ff_samp0_I ( 44 | .d(clk_slow), 45 | .q(clk_samp[0]), 46 | .c(clk_fast), 47 | .r(rst) 48 | ); 49 | 50 | ice40_serdes_dff #( 51 | .NEG(NEG_EDGE), 52 | .RST(1), 53 | .BEL({BEL_BASE, "/lc6"}) 54 | ) ff_samp1_I ( 55 | .d(clk_samp[0]), 56 | .q(clk_samp[1]), 57 | .c(clk_fast), 58 | .r(rst) 59 | ); 60 | 61 | // Detect falling edge, then rising edge 62 | assign edge_det[0] = edge_found[0] | (clk_samp[1] & ~clk_samp[0]); 63 | assign edge_det[1] = edge_found[1] | (clk_samp[0] & ~clk_samp[1] & edge_found[0]); 64 | 65 | ice40_serdes_dff #( 66 | .NEG(NEG_EDGE), 67 | .RST(1), 68 | .BEL({BEL_BASE, "/lc5"}) 69 | ) ff_edge0_I ( 70 | .d(edge_det[0]), 71 | .q(edge_found[0]), 72 | .c(clk_fast), 73 | .r(rst) 74 | ); 75 | 76 | ice40_serdes_dff #( 77 | .NEG(NEG_EDGE), 78 | .RST(1), 79 | .BEL({BEL_BASE, "/lc4"}) 80 | ) ff_edge1_I ( 81 | .d(edge_det[1]), 82 | .q(edge_found[1]), 83 | .c(clk_fast), 84 | .r(rst) 85 | ); 86 | 87 | // 2 bit upcounter 88 | assign cnt_next[0] = cnt_val[0] ^ edge_found[1]; 89 | assign cnt_next[1] = cnt_val[1] ^ (cnt_val[0] & edge_found[1]); 90 | 91 | ice40_serdes_dff #( 92 | .NEG(NEG_EDGE), 93 | .RST(1), 94 | .BEL({BEL_BASE, "/lc2"}) 95 | ) ff_cnt1_I ( 96 | .d(cnt_next[1]), 97 | .q(cnt_val[1]), 98 | .c(clk_fast), 99 | .r(rst) 100 | ); 101 | 102 | ice40_serdes_dff #( 103 | .NEG(NEG_EDGE), 104 | .RST(1), 105 | .BEL({BEL_BASE, "/lc1"}) 106 | ) ff_cnt0_I ( 107 | .d(cnt_next[0]), 108 | .q(cnt_val[0]), 109 | .c(clk_fast), 110 | .r(rst) 111 | ); 112 | 113 | // Final comparator 114 | SB_LUT4 #( 115 | .LUT_INIT(1 << ((4*PHASE_CMP)+2)) 116 | ) lut_sync_I[1:0] ( 117 | .I0(1'b0), 118 | .I1(edge_found[1]), 119 | .I2(cnt_val[0]), 120 | .I3(cnt_val[1]), 121 | .O(sync_next) 122 | ); 123 | 124 | ice40_serdes_dff #( 125 | .NEG(NEG_EDGE), 126 | .RST(1), 127 | .BEL({BEL_BASE, "/lc3"}) 128 | ) ff_sync1_I ( 129 | .d(sync_next[1]), 130 | .q(sync_i[1]), 131 | .c(clk_fast), 132 | .r(rst) 133 | ); 134 | 135 | ice40_serdes_dff #( 136 | .NEG(NEG_EDGE), 137 | .RST(1), 138 | .BEL({BEL_BASE, "/lc0"}) 139 | ) ff_sync0_I ( 140 | .d(sync_next[0]), 141 | .q(sync_i[0]), 142 | .c(clk_fast), 143 | .r(rst) 144 | ); 145 | 146 | // Buffer ? 147 | generate 148 | if (GLOBAL_BUF) 149 | (* BEL=BEL_GB *) 150 | SB_GB gbuf_sync_I ( 151 | .USER_SIGNAL_TO_GLOBAL_BUFFER(sync_i[0]), 152 | .GLOBAL_BUFFER_OUTPUT(sync) 153 | ); 154 | else 155 | assign sync = sync_i[0]; 156 | 157 | if (LOCAL_BUF) begin 158 | ice40_serdes_dff #( 159 | .NEG(NEG_EDGE), 160 | .BEL({BEL_COL, "/Y4/lc1"}), 161 | .SERDES_ATTR("sync_lbuf_bot") 162 | ) sync_bot ( 163 | .d(sync_i[0]), 164 | .q(), // Output will be wired by the script 165 | .c(clk_fast) 166 | ); 167 | 168 | ice40_serdes_dff #( 169 | .NEG(NEG_EDGE), 170 | .BEL({BEL_COL, "/Y26/lc1"}), 171 | .SERDES_ATTR("sync_lbuf_top") 172 | ) sync_top ( 173 | .d(sync_i[1]), 174 | .q(), 175 | .c(clk_fast) 176 | ); 177 | end 178 | endgenerate 179 | 180 | endmodule 181 | -------------------------------------------------------------------------------- /rtl/ice40_spi_wb.v: -------------------------------------------------------------------------------- 1 | /* 2 | * ice40_spi_wb.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Wishbone wrapper to use the SB_SPI hardmacro a bit more easily 7 | * 8 | * Copyright (C) 2020 Sylvain Munaut 9 | * SPDX-License-Identifier: CERN-OHL-P-2.0 10 | */ 11 | 12 | `default_nettype none 13 | 14 | module ice40_spi_wb #( 15 | parameter integer N_CS = 1, 16 | parameter integer WITH_IOB = 1, 17 | parameter integer UNIT = 0, // Unit 0 or 1 18 | 19 | // auto 20 | parameter integer H = N_CS - 1 21 | )( 22 | // IO 23 | // IOBs included 24 | inout wire pad_mosi, 25 | inout wire pad_miso, 26 | inout wire pad_clk, 27 | output wire [ H:0] pad_csn, 28 | 29 | // Raw signals 30 | input wire sio_mosi_i, 31 | output wire sio_mosi_o, 32 | output wire sio_mosi_oe, 33 | 34 | input wire sio_miso_i, 35 | output wire sio_miso_o, 36 | output wire sio_miso_oe, 37 | 38 | input wire sio_clk_i, 39 | output wire sio_clk_o, 40 | output wire sio_clk_oe, 41 | 42 | output wire [ H:0] sio_csn_o, 43 | output wire [ H:0] sio_csn_oe, 44 | 45 | // Wishbone interface 46 | input wire [ 3:0] wb_addr, 47 | output wire [31:0] wb_rdata, 48 | input wire [31:0] wb_wdata, 49 | input wire wb_we, 50 | input wire wb_cyc, 51 | output wire wb_ack, 52 | 53 | // Aux signals 54 | output wire irq, 55 | output wire wakeup, 56 | 57 | // Common 58 | input wire clk, 59 | input wire rst 60 | ); 61 | 62 | // Signals 63 | // ------- 64 | 65 | wire [7:0] sb_addr; 66 | wire [7:0] sb_di; 67 | wire [7:0] sb_do; 68 | wire sb_rw; 69 | wire sb_stb; 70 | wire sb_ack; 71 | 72 | wire [3:0] sio_csn_o_i; 73 | wire [3:0] sio_csn_oe_i; 74 | 75 | 76 | // Hard block 77 | // ---------- 78 | 79 | `ifndef SIM 80 | SB_SPI #( 81 | .BUS_ADDR74(UNIT ? "0b0010" : "0b0000") 82 | ) spi_I ( 83 | .SBCLKI (clk), 84 | .SBRWI (sb_rw), 85 | .SBSTBI (sb_stb), 86 | .SBADRI7 (sb_addr[7]), 87 | .SBADRI6 (sb_addr[6]), 88 | .SBADRI5 (sb_addr[5]), 89 | .SBADRI4 (sb_addr[4]), 90 | .SBADRI3 (sb_addr[3]), 91 | .SBADRI2 (sb_addr[2]), 92 | .SBADRI1 (sb_addr[1]), 93 | .SBADRI0 (sb_addr[0]), 94 | .SBDATI7 (sb_di[7]), 95 | .SBDATI6 (sb_di[6]), 96 | .SBDATI5 (sb_di[5]), 97 | .SBDATI4 (sb_di[4]), 98 | .SBDATI3 (sb_di[3]), 99 | .SBDATI2 (sb_di[2]), 100 | .SBDATI1 (sb_di[1]), 101 | .SBDATI0 (sb_di[0]), 102 | .MI (sio_miso_i), 103 | .SI (sio_mosi_i), 104 | .SCKI (sio_clk_i), 105 | .SCSNI (1'b1), 106 | .SBDATO7 (sb_do[7]), 107 | .SBDATO6 (sb_do[6]), 108 | .SBDATO5 (sb_do[5]), 109 | .SBDATO4 (sb_do[4]), 110 | .SBDATO3 (sb_do[3]), 111 | .SBDATO2 (sb_do[2]), 112 | .SBDATO1 (sb_do[1]), 113 | .SBDATO0 (sb_do[0]), 114 | .SBACKO (sb_ack), 115 | .SPIIRQ (irq), 116 | .SPIWKUP (wakeup), 117 | .SO (sio_miso_o), 118 | .SOE (sio_miso_oe), 119 | .MO (sio_mosi_o), 120 | .MOE (sio_mosi_oe), 121 | .SCKO (sio_clk_o), 122 | .SCKOE (sio_clk_oe), 123 | .MCSNO3 (sio_csn_o_i[3]), 124 | .MCSNO2 (sio_csn_o_i[2]), 125 | .MCSNO1 (sio_csn_o_i[1]), 126 | .MCSNO0 (sio_csn_o_i[0]), 127 | .MCSNOE3 (sio_csn_oe_i[3]), 128 | .MCSNOE2 (sio_csn_oe_i[2]), 129 | .MCSNOE1 (sio_csn_oe_i[1]), 130 | .MCSNOE0 (sio_csn_oe_i[0]) 131 | ); 132 | `else 133 | assign sb_ack = sb_stb; 134 | assign sb_do = 8'h00; 135 | `endif 136 | 137 | 138 | // IOB (if needed) 139 | // --------------- 140 | 141 | assign sio_csn_o = sio_csn_o_i[H:0]; 142 | assign sio_csn_oe = sio_csn_oe_i[H:0]; 143 | 144 | generate 145 | if (WITH_IOB) begin 146 | 147 | // IOB for main signals 148 | SB_IO #( 149 | .PIN_TYPE(6'b101001), 150 | .PULLUP(1'b1) 151 | ) spi_io_I[2:0] ( 152 | .PACKAGE_PIN ({pad_mosi, pad_miso, pad_clk }), 153 | .OUTPUT_ENABLE({sio_mosi_oe, sio_miso_oe, sio_clk_oe}), 154 | .D_OUT_0 ({sio_mosi_o, sio_miso_o, sio_clk_o }), 155 | .D_IN_0 ({sio_mosi_i, sio_miso_i, sio_clk_i }) 156 | ); 157 | 158 | // Bypass OE for CS_n lines 159 | assign pad_csn = sio_csn_o_i[H:0]; 160 | end 161 | endgenerate 162 | 163 | 164 | // Bus interface 165 | // ------------- 166 | 167 | assign sb_addr = { (UNIT ? 4'h2 : 4'h0), wb_addr }; 168 | assign sb_di = wb_wdata[7:0]; 169 | assign sb_rw = wb_we; 170 | assign sb_stb = wb_cyc; 171 | 172 | assign wb_rdata = { 24'h0000, wb_cyc ? sb_do : 8'h00 }; 173 | assign wb_ack = sb_ack; 174 | 175 | endmodule // ice40_spi_wb 176 | -------------------------------------------------------------------------------- /rtl/ice40_spram_gen.v: -------------------------------------------------------------------------------- 1 | /* 2 | * ice40_spram_gen.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-P-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module ice40_spram_gen #( 13 | parameter integer ADDR_WIDTH = 15, 14 | parameter integer DATA_WIDTH = 32, 15 | 16 | // auto 17 | parameter integer MASK_WIDTH = (DATA_WIDTH + 3) / 4, 18 | 19 | parameter integer AL = ADDR_WIDTH - 1, 20 | parameter integer DL = DATA_WIDTH - 1, 21 | parameter integer ML = MASK_WIDTH - 1 22 | )( 23 | input wire [AL:0] addr, 24 | output reg [DL:0] rd_data, 25 | input wire rd_ena, // Write WILL corrupt read data 26 | input wire [DL:0] wr_data, 27 | input wire [ML:0] wr_mask, 28 | input wire wr_ena, 29 | input wire clk 30 | ); 31 | 32 | genvar x, y; 33 | 34 | // Constants 35 | // --------- 36 | 37 | localparam integer ND = 1 << (ADDR_WIDTH - 14); 38 | localparam integer NW = (DATA_WIDTH + 15) / 16; 39 | 40 | localparam integer MSW = (ADDR_WIDTH > 14) ? (ADDR_WIDTH - 14) : 0; 41 | localparam integer MDW = NW * 16; 42 | localparam integer MMW = NW * 4; 43 | 44 | initial 45 | $display("ice40_spram_gen: (%dx%d) -> %dx%d array of SPRAM\n", (1< 7 | * SPDX-License-Identifier: CERN-OHL-P-2.0 8 | */ 9 | 10 | `default_nettype none 11 | 12 | module ice40_spram_wb #( 13 | parameter integer AW = 14, 14 | parameter integer DW = 32, 15 | parameter integer ZERO_RDATA = 0, 16 | 17 | // auto 18 | parameter integer MW = (DW / 8) 19 | )( 20 | // Wishbone interface 21 | input wire [AW-1:0] wb_addr, 22 | output wire [DW-1:0] wb_rdata, 23 | input wire [DW-1:0] wb_wdata, 24 | input wire [MW-1:0] wb_wmsk, 25 | input wire wb_we, 26 | input wire wb_cyc, 27 | output wire wb_ack, 28 | 29 | // Common 30 | input wire clk, 31 | input wire rst 32 | ); 33 | 34 | // Signals 35 | // ------- 36 | 37 | genvar i; 38 | 39 | wire [ DW -1:0] rdata; 40 | wire [(DW/4)-1:0] msk_nibble; 41 | wire we_i; 42 | reg ack_i; 43 | 44 | 45 | // Glue 46 | // ---- 47 | 48 | assign we_i = wb_cyc & wb_we & ~ack_i; 49 | 50 | always @(posedge clk or posedge rst) 51 | if (rst) 52 | ack_i <= 1'b0; 53 | else 54 | ack_i <= wb_cyc & ~ack_i; 55 | 56 | assign wb_ack = ack_i; 57 | 58 | 59 | // SPRAMs 60 | // ------ 61 | 62 | generate 63 | // SPRAM mask is per 4 bits 64 | for (i=0; i<(DW/4); i=i+1) 65 | assign msk_nibble[i] = wb_wmsk[i/2]; 66 | 67 | // If needed, zero rdata during inactive cycles 68 | if (ZERO_RDATA) 69 | assign wb_rdata = ack_i ? rdata : { (DW){1'b0} }; 70 | else 71 | assign wb_rdata = rdata; 72 | endgenerate 73 | 74 | ice40_spram_gen #( 75 | .ADDR_WIDTH(AW), 76 | .DATA_WIDTH(DW) 77 | ) spram_I ( 78 | .addr(wb_addr), 79 | .rd_data(rdata), 80 | .rd_ena(1'b1), 81 | .wr_data(wb_wdata), 82 | .wr_mask(msk_nibble), 83 | .wr_ena(we_i), 84 | .clk(clk) 85 | ); 86 | 87 | endmodule // ice40_spram_wb 88 | -------------------------------------------------------------------------------- /sim/ice40_ebr_tb.v: -------------------------------------------------------------------------------- 1 | /* 2 | * ice40_ebr_tb.v 3 | * 4 | * vim: ts=4 sw=4 5 | * 6 | * Copyright (C) 2020 Sylvain Munaut 7 | * SPDX-License-Identifier: CERN-OHL-P-2.0 8 | */ 9 | 10 | `default_nettype none 11 | `timescale 1ns/100ps 12 | 13 | module ice40_ebr_tb #( 14 | parameter integer READ_MODE = 2, /* 0 = 256x16, 1 = 512x8 */ 15 | parameter integer WRITE_MODE = 1, /* 2 = 1024x4, 3 = 2048x2 */ 16 | parameter integer MASK_WORKAROUND = 1 17 | ); 18 | 19 | // Config 20 | // ------ 21 | 22 | localparam integer WAW = 8 + WRITE_MODE; 23 | localparam integer WDW = 16 / (1 << WRITE_MODE); 24 | localparam integer RAW = 8 + READ_MODE; 25 | localparam integer RDW = 16 / (1 << READ_MODE); 26 | 27 | 28 | // Signals 29 | // ------- 30 | 31 | reg [WAW-1:0] wr_addr; 32 | reg [WDW-1:0] wr_data; 33 | reg [WDW-1:0] wr_mask; 34 | reg wr_ena; 35 | 36 | reg [RAW-1:0] rd_addr; 37 | wire [RDW-1:0] rd_data; 38 | reg rd_ena; 39 | 40 | reg clk = 0; 41 | reg rst = 1; 42 | 43 | 44 | // Test bench 45 | // ---------- 46 | 47 | // Setup recording 48 | initial begin 49 | $dumpfile("ice40_ebr_tb.vcd"); 50 | $dumpvars(0,ice40_ebr_tb); 51 | end 52 | 53 | // Reset pulse 54 | initial begin 55 | # 200 rst = 0; 56 | # 1000000 $finish; 57 | end 58 | 59 | // Clocks 60 | always #10 clk = !clk; 61 | 62 | 63 | 64 | // DUT 65 | // --- 66 | 67 | ice40_ebr #( 68 | .READ_MODE(READ_MODE), 69 | .WRITE_MODE(WRITE_MODE), 70 | .MASK_WORKAROUND(MASK_WORKAROUND) 71 | ) dut_I ( 72 | .wr_addr(wr_addr), 73 | .wr_data(wr_data), 74 | .wr_mask(wr_mask), 75 | .wr_ena(wr_ena), 76 | .wr_clk(clk), 77 | .rd_addr(rd_addr), 78 | .rd_data(rd_data), 79 | .rd_ena(rd_ena), 80 | .rd_clk(clk) 81 | ); 82 | 83 | 84 | reg mode; 85 | 86 | always @(posedge clk) 87 | begin 88 | // Writes 89 | if (!mode) begin 90 | wr_addr <= wr_addr + wr_ena; 91 | wr_data <= $random; 92 | wr_mask <= 8'hf0; 93 | wr_ena <= ~&wr_addr; 94 | mode <= mode ^ &wr_addr; 95 | end 96 | 97 | // Reads 98 | if (mode) begin 99 | rd_addr <= rd_addr + rd_ena; 100 | rd_ena <= ~&rd_addr; 101 | mode <= mode ^ &rd_addr; 102 | end 103 | 104 | // Reset 105 | if (rst) begin 106 | wr_addr <= 0; 107 | wr_ena <= 1'b0; 108 | rd_addr <= 0; 109 | rd_ena <= 1'b0; 110 | mode <= 1'b0; 111 | end 112 | end 113 | 114 | endmodule 115 | -------------------------------------------------------------------------------- /sw/serdes-nextpnr-place.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Custom placement script to be executed by nextpnr in the 4 | # pre-place (post-pack) phase. 5 | # 6 | # Copyright (C) 2019-2020 Sylvain Munaut 7 | # SPDX-License-Identifier: MIT 8 | # 9 | 10 | from collections import namedtuple 11 | import re 12 | 13 | 14 | # SerDes group numbers: 15 | # [15:12] Group 16 | # [11: 8] SubGroup 17 | # [ 7: 4] Type 18 | # [ 3] n/a 19 | # [ 2: 0] LC number 20 | # 21 | # SubGroups: 22 | # 23 | # 0 Data Out path 0 24 | # 1 Data Out path 1 25 | # 2 Data Out Enable 26 | # 27 | # 4 Data In path 0 28 | # 5 Data In path 1 29 | # 6 Data In common 30 | # 31 | # 32 | # Types: 33 | # 34 | # 0 OSERDES Capture 35 | # 1 OSERDES Shift 36 | # 2 OSERDES NegEdge Delay 37 | # 38 | # 8 ISERDES Slow Capture 39 | # 9 ISERDES Fast Capture 40 | # a ISERDES Shift 41 | # b ISERDES PreMux 42 | # 43 | # 44 | # Placement priority 45 | # 46 | # type near 47 | # 2 io Output Neg Edge delay 48 | # b io Input Pre mux 49 | # a io Input Shift 50 | # 9 'a' Input Fast Capture 51 | # 1 io Output Shift 52 | # 0 '1' Output Capture 53 | # 8 '9's Input Slow Capture 54 | # 55 | 56 | 57 | class BEL(namedtuple('BEL', 'x y z')): 58 | 59 | @classmethod 60 | def from_json_attr(kls, v): 61 | def to_int(s): 62 | return int(re.sub(r'[^\d-]+', '', s)) 63 | return kls(*[to_int(x) for x in v.split('/', 3)]) 64 | 65 | def distance(self, ob): 66 | return abs(self.x - ob.x) + abs(self.y - ob.y) 67 | 68 | 69 | class ControlGroup(namedtuple('ControlGroup', 'clk rst ena neg')): 70 | 71 | @classmethod 72 | def from_lc(kls, lc): 73 | netname = lambda lc, p: lc.ports[p].net.name if (lc.ports[p].net is not None) else None 74 | return kls( 75 | netname(lc, 'CLK'), 76 | netname(lc, 'SR'), 77 | netname(lc, 'CEN'), 78 | lc.params['NEG_CLK'] == '1' 79 | ) 80 | 81 | 82 | class FullCellId(namedtuple('SDGId', 'gid sid typ lc')): 83 | 84 | @classmethod 85 | def from_json_attr(kls, v): 86 | return kls( 87 | v >> 12, 88 | (v >> 8) & 0xf, 89 | (v >> 4) & 0xf, 90 | (v >> 0) & 0x7 91 | ) 92 | 93 | 94 | class SerDesGroup: 95 | 96 | def __init__(self, gid): 97 | self.gid = gid 98 | self.blocks = {} 99 | self.io = None 100 | 101 | def add_lc(self, lc, fcid=None): 102 | # Get Full Cell ID if not provided 103 | if fcid is None: 104 | grp = int(lc.attrs['SERDES_GRP'], 2) 105 | fcid = FullCellId.from_json_attr(grp) 106 | 107 | # Add to the cell list 108 | if (fcid.sid, fcid.typ) not in self.blocks: 109 | self.blocks[(fcid.sid, fcid.typ)] = SerDesBlock(self, fcid.sid, fcid.typ) 110 | 111 | self.blocks[(fcid.sid, fcid.typ)].add_lc(lc, fcid=fcid) 112 | 113 | def analyze(self): 114 | # Process all blocks 115 | for blk in self.blocks.values(): 116 | # Analyze 117 | blk.analyze() 118 | 119 | # Check IO 120 | if blk.io is not None: 121 | if (self.io is not None) and (self.io != blk.io): 122 | raise RuntimeError(f'Incompatible IO sites found in SerDes group {self.gid}: {self.io} vs {blk.io}') 123 | self.io = blk.io 124 | 125 | 126 | class SerDesBlock: 127 | 128 | NAMES = { 129 | 0x0: 'OSERDES Capture', 130 | 0x1: 'OSERDES Shift', 131 | 0x2: 'OSERDES NegEdge Delay', 132 | 0x8: 'ISERDES Slow Capture', 133 | 0x9: 'ISERDES Fast Capture', 134 | 0xa: 'ISERDES Shift', 135 | 0xb: 'ISERDES PreMux', 136 | } 137 | 138 | def __init__(self, group, sid, typ): 139 | # Identity 140 | self.group = group 141 | self.sid = sid 142 | self.typ = typ 143 | 144 | # Container 145 | self.lcs = 8 * [None] 146 | self.io = None 147 | self.cg = None 148 | 149 | def __str__(self): 150 | return f'SerDesBlock({self.sid:x}/{self.typ:x} {self.NAMES[self.typ]})' 151 | 152 | def _find_io_site_for_lc(self, lc): 153 | # Check in/out ports 154 | for pn in [ 'I0', 'I1', 'I2', 'I3', 'O' ]: 155 | n = lc.ports[pn].net 156 | if (n is None) or n.name.startswith('$PACKER_'): 157 | continue 158 | pl = [ n.driver ] + list(n.users) 159 | for p in pl: 160 | if (p.cell.type == 'SB_IO') and ('BEL' in p.cell.attrs): 161 | return BEL.from_json_attr(p.cell.attrs['BEL']) 162 | return None 163 | 164 | def add_lc(self, lc, fcid=None): 165 | # Get Full Cell ID if not provided 166 | if fcid is None: 167 | grp = int(lc.attrs['SERDES_GRP'], 2) 168 | fcid = FullCellId.from_json_attr(grp) 169 | 170 | # Add to LCs 171 | if self.lcs[fcid.lc] is not None: 172 | raise RuntimeError(f'Duplicate LC for FullCellId {fcid}') 173 | 174 | self.lcs[fcid.lc] = lc 175 | 176 | def find_io_site(self): 177 | for lc in self.lcs: 178 | if lc is None: 179 | continue 180 | s = self._find_io_site_for_lc(lc) 181 | if s is not None: 182 | return s 183 | return None 184 | 185 | def analyze(self): 186 | # Check and truncate LC array 187 | l = len(self) 188 | if not all([x is not None for x in self.lcs[0:l]]): 189 | raise RuntimeError(f'Invalid group in block {self.group.gid}/{self.sid}/{self.typ}') 190 | 191 | self.lcs = self.lcs[0:l] 192 | 193 | # Identify IO site connection if there is one 194 | self.io = self.find_io_site() 195 | 196 | # Identify the control group 197 | self.cg = ControlGroup.from_lc(self.lcs[0]) 198 | 199 | def assign_bel(self, base_bel, zofs=0): 200 | for i, lc in enumerate(self.lcs): 201 | lc.setAttr('BEL', 'X%d/Y%d/lc%d' % (base_bel.x, base_bel.y, base_bel.z + zofs + i)) 202 | 203 | def __len__(self): 204 | return sum([x is not None for x in self.lcs]) 205 | 206 | 207 | class PlacerSite: 208 | 209 | def __init__(self, pos): 210 | self.pos = pos 211 | self.free = 8 212 | self.blocks = [] 213 | self.cg = None 214 | 215 | def valid_for_block(self, blk): 216 | return (self.free >= len(blk)) and ( 217 | (self.cg is None) or 218 | (blk.cg is None) or 219 | (self.cg == blk.cg) 220 | ) 221 | 222 | def add_block(self, blk): 223 | # Assign the block into position 224 | pos = BEL(self.pos.x, self.pos.y, 8-self.free) 225 | blk.assign_bel(pos) 226 | 227 | # Add to blocks here 228 | self.blocks.append(blk) 229 | 230 | # Update constrainsts 231 | self.cg = blk.cg 232 | self.free -= len(blk) 233 | 234 | return pos 235 | 236 | 237 | class Placer: 238 | 239 | PRIORITY = [ 240 | # Type Place Target 241 | (0x2, lambda p, b: b.group.io), 242 | (0xb, lambda p, b: b.group.io), 243 | (0xa, lambda p, b: b.group.io), 244 | (0x9, lambda p, b: p.pos_of( b.group.blocks[(4|(b.sid & 1), 0xa)] ) ), 245 | (0x1, lambda p, b: b.group.io), 246 | (0x0, lambda p, b: p.pos_of( b.group.blocks[(b.sid, 0x1)] ) ), 247 | (0x8, lambda p, b: p.pos_of( b.group.blocks[(4, 0xa)], b.group.blocks.get((5, 0xa)) ) ), 248 | ] 249 | 250 | PLACE_PREF = [ 251 | # Uofs Vofs 252 | # (U is parallel to IO bank direction, V is perpendicular) 253 | ( 0, 1), 254 | (-1, 1), 255 | ( 1, 1), 256 | (-1, 0), 257 | ( 1, 0), 258 | ( 0, -1), 259 | (-1, -1), 260 | ( 1, -1), 261 | ( 0, 1), 262 | ( 0, 2), 263 | ( 0, 3), 264 | ( 0, 4), 265 | (-1, 1), 266 | ( 1, 1), 267 | (-1, 2), 268 | ( 1, 2), 269 | (-1, 3), 270 | ( 1, 3), 271 | (-1, 4), 272 | ( 1, 4), 273 | ( 0, 5), 274 | (-1, 5), 275 | ( 1, 5), 276 | ] 277 | 278 | def __init__(self, groups): 279 | # Save groups to place 280 | self.groups = groups 281 | 282 | # Generate site grid 283 | self.m_fwd = {} 284 | self.m_back = {} 285 | 286 | for bel_name in ctx.getBels(): 287 | if not bel_name.endswith('/lc0'): 288 | continue 289 | bel = BEL.from_json_attr(bel_name) 290 | self.m_fwd[bel] = PlacerSite(bel) 291 | 292 | def _blocks_by_type(self, typ): 293 | r = [] 294 | for grp in self.groups: 295 | for blk in grp.blocks.values(): 296 | if blk.typ == typ: 297 | r.append(blk) 298 | return sorted(r, key=lambda b: (b.group.gid, b.sid)) 299 | 300 | def place(self): 301 | # Scan by priority order 302 | for typ, fn in self.PRIORITY: 303 | # Collect all blocks per type and sorted by gid,sid 304 | blocks = self._blocks_by_type(typ) 305 | 306 | # Place each block 307 | for blk in blocks: 308 | # Get target location 309 | tgt = fn(self, blk) 310 | 311 | if type(tgt) == list: 312 | x = int(round(sum([b.x for b in tgt]) / len(tgt))) 313 | y = int(round(sum([b.y for b in tgt]) / len(tgt))) 314 | tgt = BEL(x, y, 0) 315 | 316 | # Scan placement preference and try to place 317 | for uofs, vofs in self.PLACE_PREF: 318 | # Convert U/V to X/Y depending on IO bank 319 | io = blk.group.io 320 | 321 | if io.x == 0: 322 | # Left 323 | xofs = vofs 324 | yofs = uofs 325 | 326 | elif io.y == 0: 327 | # Bottom 328 | xofs = uofs 329 | yofs = vofs 330 | 331 | elif io.x > io.y: 332 | # Right 333 | xofs = -vofs 334 | yofs = -uofs 335 | 336 | else: 337 | # Top 338 | xofs = -uofs 339 | yofs = -vofs 340 | 341 | # Apply offset and check if it's valid 342 | p = BEL(tgt.x + xofs, tgt.y + yofs, 0) 343 | 344 | if (p in self.m_fwd) and (self.m_fwd[p].valid_for_block(blk)): 345 | self.place_block(blk, p) 346 | break 347 | 348 | else: 349 | raise RuntimeError(f'Unable to place {blk}') 350 | 351 | # Debug 352 | if debug: 353 | for g in self.groups: 354 | print(f"Group {g.gid} for IO {g.io}") 355 | for b in g.blocks.values(): 356 | print(f"\t{str(b):40s}: {len(b)} LCs placed @ {self.pos_of(b)}") 357 | print() 358 | 359 | def place_block(self, blk, pos): 360 | self.m_back[blk] = self.m_fwd[pos].add_block(blk) 361 | 362 | def pos_of(self, *blocks): 363 | if len(blocks) > 1: 364 | return [ self.m_back.get(b) for b in blocks if b is not None ] 365 | else: 366 | return self.m_back.get(blocks[0]) 367 | 368 | 369 | 370 | # ---------------------------------------------------------------------------- 371 | # Main 372 | # ---------------------------------------------------------------------------- 373 | 374 | debug = True 375 | 376 | 377 | # Collect 378 | # ------- 379 | 380 | groups = {} 381 | 382 | # Maps PLB to sync_nets (all those nets are equivalent but duplicated by 383 | # different LCs in the PLB for better routing to the local buffer) 384 | sync_lbuf_plb2nets = {} 385 | 386 | # Maps each PLB to a list of ( BEL, net ), each corresponding to a 387 | # local buffer position and its output. 388 | sync_lbuf_plb2bufs = {} 389 | 390 | 391 | for n,c in ctx.cells: 392 | # Filter out dummy 'BEL' attributes 393 | if 'BEL' in c.attrs: 394 | if not c.attrs['BEL'].strip(): 395 | c.unsetAttr('BEL') 396 | 397 | # Special processing 398 | if 'SERDES_ATTR' in c.attrs: 399 | attr = c.attrs['SERDES_ATTR'] 400 | c.unsetAttr('SERDES_ATTR') 401 | 402 | # Local sync buffer 403 | if attr.startswith('sync_lbuf'): 404 | # Sync source 405 | src_net = c.ports['I0'].net 406 | src_plb = BEL.from_json_attr(src_net.driver.cell.attrs['BEL'])[0:2] 407 | dst_net = c.ports['O'].net 408 | 409 | # Collect 410 | sync_lbuf_plb2nets.setdefault(src_plb, []).append(src_net.name) 411 | sync_lbuf_plb2bufs.setdefault(src_plb, []).append( (BEL.from_json_attr(c.attrs['BEL']), dst_net.name) ) 412 | 413 | # Does the cell need grouping ? 414 | if 'SERDES_GRP' in c.attrs: 415 | # Get group 416 | grp = int(c.attrs['SERDES_GRP'], 2) 417 | c.unsetAttr('SERDES_GRP') 418 | 419 | # Skip invalid/dummy 420 | if grp == 0xffffffff: 421 | continue 422 | 423 | # Add LC to our list 424 | fcid = FullCellId.from_json_attr(grp) 425 | 426 | if fcid.gid not in groups: 427 | groups[fcid.gid] = SerDesGroup(fcid.gid) 428 | 429 | groups[fcid.gid].add_lc(c, fcid=fcid) 430 | 431 | 432 | # Analyze groups 433 | # -------------- 434 | 435 | for g in groups.values(): 436 | g.analyze() 437 | 438 | 439 | # Execute placer 440 | # -------------- 441 | 442 | placer = Placer(groups.values()) 443 | placer.place() 444 | 445 | 446 | # Process local buffers 447 | # --------------------- 448 | 449 | def lbuf_build_map(src_net_map, dst_net_map): 450 | rv = {} 451 | for k, v in dst_net_map.items(): 452 | for n in src_net_map[k]: 453 | rv[n] = v 454 | return rv 455 | 456 | 457 | def lbuf_reconnect(net_map, cells): 458 | # Scan all cells 459 | for lc in cells: 460 | # Scan all ports 461 | for pn in ['I0', 'I1', 'I2', 'I3', 'CEN']: 462 | n = lc.ports[pn].net 463 | cb = BEL.from_json_attr(lc.attrs['BEL']) 464 | if (n is not None) and (n.name in net_map): 465 | # Find closest buffers 466 | print(cb, sorted(net_map[n.name], key=lambda x: cb.distance(x[0]))) 467 | nn = sorted(net_map[n.name], key=lambda x: cb.distance(x[0]))[0][1] 468 | 469 | # Reconnect 470 | ctx.disconnectPort(lc.name, pn) 471 | ctx.connectPort(nn, lc.name, pn) 472 | 473 | 474 | sync_lbuf = lbuf_build_map(sync_lbuf_plb2nets, sync_lbuf_plb2bufs) 475 | 476 | for g in groups.values(): 477 | for blk in g.blocks.values(): 478 | lbuf_reconnect(sync_lbuf, blk.lcs) 479 | --------------------------------------------------------------------------------