├── .bazelrc ├── .bazelversion ├── .gitignore ├── COPYING ├── README.md ├── WORKSPACE ├── boards ├── colorlight-70 │ ├── BRAM2Load.v │ ├── BUILD.bazel │ ├── Board.bsv │ └── colorlight-70.lpf ├── qf100 │ ├── BUILD.bazel │ ├── Board.bsv │ ├── bram.bin │ └── sky130_sram_2kbyte_1rw1r_32x512_8.v └── ulx3s │ ├── BRAM2Load.v │ ├── BUILD.bazel │ ├── Board.bsv │ ├── bram.bin │ └── ulx3s_v20.lpf ├── build ├── BUILD.bazel ├── bluespec │ ├── BUILD.bazel │ ├── bluetcl.sh │ ├── bsc.sh │ ├── bscwrap.cc │ ├── rules.bzl │ └── subprocess.hpp ├── platforms │ └── BUILD.bazel ├── providers.bzl ├── synthesis │ ├── BUILD.bazel │ ├── add_power_pins.py │ └── rules.bzl └── utils.bzl ├── fpga ├── BUILD.bazel └── ECP5.bsv ├── hub75 ├── BUILD.bazel ├── Hub75.bsv ├── Tb.bsv └── gammagen.py ├── lanai ├── BUILD.bazel ├── CPU_ALU.bsv ├── CPU_Compute.bsv ├── CPU_Defs.bsv ├── CPU_Fetch.bsv ├── CPU_Memory.bsv ├── CPU_RegisterFile.bsv ├── Lanai_CPU.bsv ├── Lanai_IFC.bsv ├── Lanai_Memory.bsv ├── Tb.bsv ├── bram.bin ├── frontend │ ├── BUILD.bazel │ ├── LanaiFrontend.bsv │ └── SPIFlashController.bsv ├── qasm │ ├── BUILD.bazel │ └── qasm.py └── test.py ├── systems └── qf100 │ ├── BUILD.bazel │ ├── QF100.bsv │ ├── SPIFlashEmulator.bsv │ ├── Sky130SRAM.bsv │ ├── Tb.bsv │ └── flash.bin └── wishbone ├── BUILD.bazel ├── TbCBus.bsv ├── TbConnectors.bsv ├── TbCrossbar.bsv ├── Wishbone.bsv ├── WishboneCrossbar.bsv └── peripherals ├── BUILD.bazel ├── WishboneGPIO.bsv ├── WishboneKitchenSink.bsv └── WishboneSPI.bsv /.bazelrc: -------------------------------------------------------------------------------- 1 | build --host_platform=@io_tweag_rules_nixpkgs//nixpkgs/platforms:host 2 | build --incompatible_use_cc_configure_from_rules_cc 3 | build --host_crosstool_top=@local_config_cc//:toolchain 4 | build --platforms=//build/platforms:ulx3s_85f 5 | build --experimental_allow_unresolved_symlinks 6 | build --host_cxxopt='-std=c++17' 7 | -------------------------------------------------------------------------------- /.bazelversion: -------------------------------------------------------------------------------- 1 | 4.2.2 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bazel-* 2 | **swp 3 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | --2022-03-18 22:26:54-- https://www.gnu.org/licenses/gpl-3.0.txt 2 | Resolving www.gnu.org (www.gnu.org)... 209.51.188.116, 2001:470:142:5::116 3 | Connecting to www.gnu.org (www.gnu.org)|209.51.188.116|:443... connected. 4 | HTTP request sent, awaiting response... 200 OK 5 | Length: 35149 (34K) [text/plain] 6 | Saving to: ‘gpl-3.0.txt’ 7 | 8 | 0K .......... .......... .......... .... 100% 166K=0.2s 9 | 10 | 2022-03-18 22:26:55 (166 KB/s) - ‘gpl-3.0.txt’ saved [35149/35149] 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | qfc 2 | === 3 | 4 | q3k's {fpga,fantasy,fancy,futile,funky} computer. An ever growing collection of Bluespec hardware stuff made by q3k. 5 | 6 | Currently: 7 | 8 | - Lanai core (3-stage, in order) 9 | - Wishbone implementations 10 | - Some _Familiar Looking_ Wishbone peripherals (SPI, GPIO) 11 | - HUB75 controller core 12 | - Bazel and nix machinery to tie the above together. 13 | 14 | qf100 15 | ----- 16 | 17 | A microcontroller based on this codebase. Lives at https://github.com/q3k/qf100 . 18 | 19 | License 20 | ------- 21 | 22 | Unless otherwise specified, all files within this repository are licensed under the GNU General Public License 3.0 or later. See COPYING for more information. 23 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace( 2 | name = "qfc", 3 | ) 4 | 5 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 6 | 7 | http_archive( 8 | name = "rules_cc", 9 | urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.0.1/rules_cc-0.0.1.tar.gz"], 10 | sha256 = "4dccbfd22c0def164c8f47458bd50e0c7148f3d92002cdb459c2a96a68498241", 11 | ) 12 | 13 | http_archive( 14 | name = "io_tweag_rules_nixpkgs", 15 | strip_prefix = "rules_nixpkgs-81f61c4b5afcf50665b7073f7fce4c1755b4b9a3", 16 | urls = ["https://github.com/tweag/rules_nixpkgs/archive/81f61c4b5afcf50665b7073f7fce4c1755b4b9a3.tar.gz"], 17 | sha256 = "33fd540d0283cf9956d0a5a640acb1430c81539a84069114beaf9640c96d221a", 18 | ) 19 | 20 | load("@io_tweag_rules_nixpkgs//nixpkgs:repositories.bzl", "rules_nixpkgs_dependencies") 21 | rules_nixpkgs_dependencies() 22 | 23 | load("@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl", "nixpkgs_git_repository", "nixpkgs_package", "nixpkgs_cc_configure") 24 | 25 | nixpkgs_git_repository( 26 | name = "nixpkgs", 27 | revision = "36f0cc6def05dea2b0e9ab0ff4b1dbd9e0d94dda", 28 | sha256 = "a40fb99f59df730883a45d78f3091e59080eace36b81139bc5bea1c453cbd7a7", 29 | ) 30 | 31 | nixpkgs_cc_configure( 32 | name = "local_config_cc", 33 | repository = "@nixpkgs//:default.nix", 34 | ) 35 | 36 | nixpkgs_package( 37 | name = "bluespec", 38 | repositories = { "nixpkgs": "@nixpkgs//:default.nix" }, 39 | build_file_content = """ 40 | load("@qfc//build/bluespec:rules.bzl", "bluespec_toolchain") 41 | 42 | sh_binary( 43 | name = "bsc", 44 | data = [ 45 | "bin/bsc" 46 | ] + glob(["lib/**", "bin/core/**"]), 47 | srcs = [ 48 | "@qfc//build/bluespec:bsc.sh", 49 | ], 50 | deps = [ 51 | "@bazel_tools//tools/bash/runfiles", 52 | ], 53 | ) 54 | 55 | sh_binary( 56 | name = "bluetcl", 57 | data = [ 58 | "bin/bluetcl" 59 | ] + glob(["lib/**", "bin/core/**"]), 60 | srcs = [ 61 | "@qfc//build/bluespec:bluetcl.sh", 62 | ], 63 | deps = [ 64 | "@bazel_tools//tools/bash/runfiles", 65 | ], 66 | ) 67 | 68 | bluespec_toolchain( 69 | name = "bsc_nixpkgs", 70 | bsc = ":bsc", 71 | bluetcl = ":bluetcl", 72 | verilog_lib = glob(["lib/Verilog/*.v"], [ 73 | "lib/Verilog/BRAM2.v", 74 | "lib/Verilog/BRAM2Load.v", 75 | "lib/Verilog/ConstrainedRandom.v", 76 | "lib/Verilog/Convert*Z.v", 77 | "lib/Verilog/InoutConnect.v", 78 | "lib/Verilog/ProbeHook.v", 79 | "lib/Verilog/RegFileLoad.v", 80 | "lib/Verilog/ResolveZ.v", 81 | "lib/Verilog/main.v", 82 | ]), 83 | ) 84 | 85 | toolchain( 86 | name = "bsc_nixpkgs_toolchain", 87 | exec_compatible_with = [ 88 | "@platforms//os:linux", 89 | ], 90 | toolchain = ":bsc_nixpkgs", 91 | toolchain_type = "@qfc//build/bluespec:toolchain_type", 92 | ) 93 | 94 | """, 95 | ) 96 | 97 | nixpkgs_package( 98 | name = "yosysflow", 99 | repositories = { "nixpkgs2": "@nixpkgs//:default.nix" }, 100 | nix_file_content = """ 101 | with import {}; symlinkJoin { 102 | name = "yosysflow"; 103 | paths = with pkgs; [ yosys nextpnr trellis ]; 104 | } 105 | """, 106 | build_file_content = """ 107 | load("@qfc//build:utils.bzl", "external_binary_tool") 108 | load("@qfc//build/synthesis:rules.bzl", "yosysflow_toolchain") 109 | 110 | external_binary_tool( 111 | name = "yosys", 112 | bin = "bin/yosys", 113 | deps = glob([ 114 | "bin/yosys-*", 115 | "share/yosys/**", 116 | ]), 117 | ) 118 | 119 | external_binary_tool( 120 | name = "nextpnr_ecp5", 121 | bin = "bin/nextpnr-ecp5", 122 | deps = [], 123 | ) 124 | 125 | external_binary_tool( 126 | name = "ecppack", 127 | bin = "bin/ecppack", 128 | deps = [], 129 | ) 130 | 131 | yosysflow_toolchain( 132 | name = "yosysflow_nixpkgs_ecp5", 133 | yosys = ":yosys", 134 | nextpnr = ":nextpnr_ecp5", 135 | packer = ":ecppack", 136 | ) 137 | 138 | toolchain( 139 | name = "yosysflow_nixpkgs_ecp5_toolchain", 140 | exec_compatible_with = [ 141 | "@platforms//os:linux", 142 | ], 143 | target_compatible_with = [ 144 | "@qfc//build/platforms:ecp5", 145 | ], 146 | toolchain = ":yosysflow_nixpkgs_ecp5", 147 | toolchain_type = "@qfc//build/synthesis:toolchain_type", 148 | ) 149 | 150 | """, 151 | ) 152 | 153 | register_toolchains( 154 | "@bluespec//:bsc_nixpkgs_toolchain", 155 | "@yosysflow//:yosysflow_nixpkgs_ecp5_toolchain", 156 | ) 157 | -------------------------------------------------------------------------------- /boards/colorlight-70/BRAM2Load.v: -------------------------------------------------------------------------------- 1 | // Taken from github.com/B-Lang-org/bsc, file src/Verilog/BRAM2Load.v. 2 | // Modified to remove write enable from second port, allowing Yosys to infer 3 | // that this is not a true dual-ported BRAM. 4 | // 5 | // Copyright (c) 2020 Bluespec, Inc. All rights reserved. 6 | // Redistribution and use in source and binary forms, with or without 7 | // modification, are permitted provided that the following conditions are 8 | // met: 9 | // 10 | // 1. Redistributions of source code must retain the above copyright 11 | // notice, this list of conditions and the following disclaimer. 12 | // 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the 16 | // distribution. 17 | // 18 | // 3. Neither the name of the copyright holder nor the names of its 19 | // contributors may be used to endorse or promote products derived 20 | // from this software without specific prior written permission. 21 | // 22 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | // 34 | // SPDX-License-Identifier: BSD-3-Clause 35 | 36 | `ifdef BSV_ASSIGNMENT_DELAY 37 | `else 38 | `define BSV_ASSIGNMENT_DELAY 39 | `endif 40 | 41 | // Dual-Ported BRAM (WRITE FIRST) 42 | module BRAM2Load(CLKA, 43 | ENA, 44 | WEA, 45 | ADDRA, 46 | DIA, 47 | DOA, 48 | CLKB, 49 | ENB, 50 | WEB, 51 | ADDRB, 52 | DIB, 53 | DOB 54 | ); 55 | 56 | parameter FILENAME = ""; 57 | parameter PIPELINED = 0; 58 | parameter ADDR_WIDTH = 1; 59 | parameter DATA_WIDTH = 1; 60 | parameter MEMSIZE = 1; 61 | parameter BINARY = 0; 62 | 63 | input CLKA; 64 | input ENA; 65 | input WEA; 66 | input [ADDR_WIDTH-1:0] ADDRA; 67 | input [DATA_WIDTH-1:0] DIA; 68 | output [DATA_WIDTH-1:0] DOA; 69 | 70 | input CLKB; 71 | input ENB; 72 | input WEB; 73 | input [ADDR_WIDTH-1:0] ADDRB; 74 | input [DATA_WIDTH-1:0] DIB; 75 | output [DATA_WIDTH-1:0] DOB; 76 | 77 | reg [DATA_WIDTH-1:0] RAM[0:MEMSIZE-1] /* synthesis syn_ramstyle="no_rw_check" */ ; 78 | reg [DATA_WIDTH-1:0] DOA_R; 79 | reg [DATA_WIDTH-1:0] DOB_R; 80 | reg [DATA_WIDTH-1:0] DOA_R2; 81 | reg [DATA_WIDTH-1:0] DOB_R2; 82 | 83 | // synopsys translate_off 84 | initial 85 | begin : init_block 86 | `ifdef BSV_NO_INITIAL_BLOCKS 87 | `else 88 | DOA_R = { ((DATA_WIDTH+1)/2) { 2'b10 } }; 89 | DOB_R = { ((DATA_WIDTH+1)/2) { 2'b10 } }; 90 | DOA_R2 = { ((DATA_WIDTH+1)/2) { 2'b10 } }; 91 | DOB_R2 = { ((DATA_WIDTH+1)/2) { 2'b10 } }; 92 | `endif // !`ifdef BSV_NO_INITIAL_BLOCKS 93 | end 94 | // synopsys translate_on 95 | 96 | initial 97 | begin : init_rom_block 98 | if (BINARY) 99 | $readmemb(FILENAME, RAM, 0, MEMSIZE-1); 100 | else 101 | $readmemh(FILENAME, RAM, 0, MEMSIZE-1); 102 | end 103 | 104 | always @(posedge CLKA) begin 105 | if (ENA) begin 106 | //if (WEA) begin 107 | // RAM[ADDRA] <= `BSV_ASSIGNMENT_DELAY DIA; 108 | // DOA_R <= `BSV_ASSIGNMENT_DELAY DIA; 109 | //end else begin 110 | DOA_R <= `BSV_ASSIGNMENT_DELAY RAM[ADDRA]; 111 | //end 112 | end 113 | DOA_R2 <= `BSV_ASSIGNMENT_DELAY DOA_R; 114 | end 115 | 116 | always @(posedge CLKB) begin 117 | if (ENB) begin 118 | if (WEB) begin 119 | RAM[ADDRB] <= `BSV_ASSIGNMENT_DELAY DIB; 120 | DOB_R <= `BSV_ASSIGNMENT_DELAY DIB; 121 | end 122 | else begin 123 | DOB_R <= `BSV_ASSIGNMENT_DELAY RAM[ADDRB]; 124 | end 125 | end 126 | DOB_R2 <= `BSV_ASSIGNMENT_DELAY DOB_R; 127 | end 128 | 129 | // Output drivers 130 | assign DOA = (PIPELINED) ? DOA_R2 : DOA_R; 131 | assign DOB = (PIPELINED) ? DOB_R2 : DOB_R; 132 | 133 | endmodule // BRAM2Load 134 | -------------------------------------------------------------------------------- /boards/colorlight-70/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//build/bluespec:rules.bzl", "bluespec_library") 2 | load("//build/synthesis:rules.bzl", "yosysflow_bitstream") 3 | 4 | bluespec_library( 5 | name = "Board", 6 | srcs = [ 7 | "Board.bsv", 8 | ], 9 | deps = [ 10 | "//fpga:ECP5", 11 | "//hub75", 12 | ], 13 | synthesize = { 14 | "Board": ["mkTop"], 15 | }, 16 | ) 17 | 18 | yosysflow_bitstream( 19 | name = 'colorlight-70', 20 | deps = [ 21 | ":Board", 22 | ], 23 | top = "mkTop", 24 | constraints = "colorlight-70.lpf", 25 | ) 26 | -------------------------------------------------------------------------------- /boards/colorlight-70/Board.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package Board; 5 | 6 | import GetPut :: *; 7 | import ClientServer :: *; 8 | import Connectable :: *; 9 | import ECP5 :: *; 10 | import RAM :: *; 11 | import FIFO :: *; 12 | import SpecialFIFOs :: *; 13 | import Probe :: *; 14 | 15 | import Hub75 :: *; 16 | 17 | module mkPatternGen (Server #(Coordinates#(4), PixelData)); 18 | FIFO#(Coordinates#(4)) fifoReq <- mkPipelineFIFO; 19 | Reg#(Bit#(32)) upcounter <- mkReg(0); 20 | 21 | rule upcount; 22 | upcounter <= upcounter + 1; 23 | endrule 24 | 25 | let timer = upcounter[24:19]; 26 | 27 | interface Put request; 28 | method Action put(Coordinates#(4) c); 29 | fifoReq.enq(c); 30 | endmethod 31 | endinterface 32 | interface Get response; 33 | method ActionValue#(PixelData) get; 34 | fifoReq.deq; 35 | let r = fifoReq.first; 36 | return PixelData { r: zeroExtend((r.x + timer) ^ r.y) << 2 37 | , b: zeroExtend(r.x ^ (r.y + timer)) << 2 38 | , g: 0 39 | }; 40 | endmethod 41 | endinterface 42 | endmodule 43 | 44 | 45 | interface Top; 46 | (* always_enabled *) 47 | method Bit#(1) led; 48 | 49 | (* always_enabled *) 50 | method Bit#(1) hub75_a; 51 | (* always_enabled *) 52 | method Bit#(1) hub75_b; 53 | (* always_enabled *) 54 | method Bit#(1) hub75_c; 55 | (* always_enabled *) 56 | method Bit#(1) hub75_d; 57 | 58 | (* always_enabled *) 59 | method Bit#(1) hub75_clk; 60 | (* always_enabled *) 61 | method Bit#(1) hub75_lat; 62 | (* always_enabled *) 63 | method Bit#(1) hub75_oe; 64 | 65 | (* always_enabled *) 66 | method Bit#(1) hub75_j5_r0; 67 | (* always_enabled *) 68 | method Bit#(1) hub75_j5_g0; 69 | (* always_enabled *) 70 | method Bit#(1) hub75_j5_b0; 71 | (* always_enabled *) 72 | method Bit#(1) hub75_j5_r1; 73 | (* always_enabled *) 74 | method Bit#(1) hub75_j5_g1; 75 | (* always_enabled *) 76 | method Bit#(1) hub75_j5_b1; 77 | (* always_enabled *) 78 | method Bit#(1) hub75_j6_r0; 79 | (* always_enabled *) 80 | method Bit#(1) hub75_j6_g0; 81 | (* always_enabled *) 82 | method Bit#(1) hub75_j6_b0; 83 | (* always_enabled *) 84 | method Bit#(1) hub75_j6_r1; 85 | (* always_enabled *) 86 | method Bit#(1) hub75_j6_g1; 87 | (* always_enabled *) 88 | method Bit#(1) hub75_j6_b1; 89 | endinterface 90 | 91 | (* synthesize, default_clock_osc="clk_25mhz", default_reset="btn_user" *) 92 | module mkTop (Top); 93 | GSR gsr <- mkGSR; 94 | Reg#(Bit#(25)) upcounter <- mkReg(0); 95 | Reg#(Bool) ledval <- mkReg(False); 96 | 97 | Hub75#(4) hub75 <- mkHub75; 98 | let patternGen <- mkPatternGen; 99 | 100 | mkConnection(hub75.pixel_data, patternGen); 101 | 102 | rule upcount; 103 | if (upcounter == 25000000) begin 104 | upcounter <= 0; 105 | ledval <= !ledval; 106 | end else begin 107 | upcounter <= upcounter + 1; 108 | end 109 | endrule 110 | 111 | method led = pack(ledval); 112 | 113 | method hub75_clk = hub75.port.clk; 114 | method hub75_lat = hub75.port.latch; 115 | method hub75_oe = hub75.port.oe; 116 | method hub75_j5_r0 = hub75.port.lines[0].r; 117 | method hub75_j5_g0 = hub75.port.lines[0].g; 118 | method hub75_j5_b0 = hub75.port.lines[0].b; 119 | method hub75_j5_r1 = hub75.port.lines[1].r; 120 | method hub75_j5_g1 = hub75.port.lines[1].g; 121 | method hub75_j5_b1 = hub75.port.lines[1].b; 122 | method hub75_j6_r0 = hub75.port.lines[2].r; 123 | method hub75_j6_g0 = hub75.port.lines[2].g; 124 | method hub75_j6_b0 = hub75.port.lines[2].b; 125 | method hub75_j6_r1 = hub75.port.lines[3].r; 126 | method hub75_j6_g1 = hub75.port.lines[3].g; 127 | method hub75_j6_b1 = hub75.port.lines[3].b; 128 | method hub75_a = hub75.port.bank[0]; 129 | method hub75_b = hub75.port.bank[1]; 130 | method hub75_c = hub75.port.bank[2]; 131 | method hub75_d = hub75.port.bank[3]; 132 | endmodule 133 | 134 | endpackage 135 | -------------------------------------------------------------------------------- /boards/colorlight-70/colorlight-70.lpf: -------------------------------------------------------------------------------- 1 | LOCATE COMP "led" SITE "P11"; 2 | IOBUF PORT "led" OPENDRAIN=ON IO_TYPE=LVCMOS33; 3 | 4 | LOCATE COMP "clk_25mhz" SITE "P6"; 5 | IOBUF PORT "clk_25mhz" IO_TYPE=LVCMOS33; 6 | 7 | LOCATE COMP "btn_user" SITE "M13"; 8 | IOBUF PORT "btn_user" IO_TYPE=LVCMOS33; 9 | 10 | 11 | LOCATE COMP "hub75_j5_r0" SITE "M11"; # J5 12 | LOCATE COMP "hub75_j5_g0" SITE "N11"; 13 | LOCATE COMP "hub75_j5_b0" SITE "P12"; 14 | LOCATE COMP "hub75_j5_r1" SITE "K15"; 15 | LOCATE COMP "hub75_j5_g1" SITE "N12"; 16 | LOCATE COMP "hub75_j5_b1" SITE "L16"; 17 | LOCATE COMP "hub75_j6_r0" SITE "K16"; # J6 18 | LOCATE COMP "hub75_j6_g0" SITE "J15"; 19 | LOCATE COMP "hub75_j6_b0" SITE "J16"; 20 | LOCATE COMP "hub75_j6_r1" SITE "J12"; 21 | LOCATE COMP "hub75_j6_g1" SITE "H15"; 22 | LOCATE COMP "hub75_j6_b1" SITE "G16"; 23 | 24 | LOCATE COMP "hub75_a" SITE "L2"; 25 | LOCATE COMP "hub75_b" SITE "K1"; 26 | LOCATE COMP "hub75_c" SITE "J5"; 27 | LOCATE COMP "hub75_d" SITE "K2"; 28 | LOCATE COMP "hub75_e" SITE "F15"; 29 | LOCATE COMP "hub75_clk" SITE "B16"; 30 | LOCATE COMP "hub75_lat" SITE "J14"; 31 | LOCATE COMP "hub75_oe" SITE "F12"; 32 | -------------------------------------------------------------------------------- /boards/qf100/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//build/bluespec:rules.bzl", "bluespec_library") 2 | load("//build/synthesis:rules.bzl", "rtl_bundle") 3 | 4 | bluespec_library( 5 | name = "QF100", 6 | srcs = [ 7 | "Board.bsv", 8 | ], 9 | deps = [ 10 | "//lanai:cpu", 11 | "//wishbone/peripherals:spi", 12 | "//wishbone/peripherals:gpio", 13 | "//systems/qf100", 14 | "//systems/qf100:sky130_sram", 15 | "//systems/qf100:spi_flash_emulator", 16 | ], 17 | synthesize = { 18 | "Board": [ 19 | "mkQF105", 20 | "mkQF100SPIFlashEmulator", 21 | ], 22 | }, 23 | data = [ 24 | ":flash.bin", 25 | ], 26 | ) 27 | 28 | rtl_bundle( 29 | name = "qf100", 30 | srcs = [ 31 | ":sky130_sram_2kbyte_1rw1r_32x512_8.v" 32 | ], 33 | outputs = { 34 | "mkLanaiCPU": [], 35 | "mkLanaiFrontend": [], 36 | "mkQF100SPI": [], 37 | "mkQF100GPIO": [], 38 | "mkQF100KSC": [], 39 | "mkQF100Fabric": [], 40 | "mkQF100FlashController": [], 41 | "sky130_sram_2kbyte_1rw1r_32x512_8_wrapper": [ 42 | "sky130_sram_2kbyte_1rw1r_32x512_8", 43 | ], 44 | "mkQF105": [ 45 | "mkLanaiCPU", 46 | "mkLanaiFrontend", 47 | "mkQF100SPI", 48 | "mkQF100GPIO", 49 | "mkQF100KSC", 50 | "mkQF100Fabric", 51 | "mkQF100FlashController", 52 | "sky130_sram_2kbyte_1rw1r_32x512_8_wrapper", 53 | ], 54 | "mkQF100SPIFlashEmulator": [], 55 | }, 56 | deps = [ 57 | ":QF100", 58 | ], 59 | ) 60 | -------------------------------------------------------------------------------- /boards/qf100/Board.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package Board; 5 | 6 | import Clocks :: *; 7 | import Connectable :: *; 8 | import TieOff :: *; 9 | 10 | import QF100 :: *; 11 | import Sky130SRAM :: *; 12 | 13 | import Lanai_IFC :: *; 14 | import Lanai_CPU :: *; 15 | import Lanai_Memory :: *; 16 | import RAM :: *; 17 | import WishboneCrossbar :: *; 18 | import WishboneSPI :: *; 19 | import WishboneGPIO :: *; 20 | 21 | import SPIFlashEmulator :: *; 22 | 23 | (* synthesize *) 24 | module mkQF100SPIFlashEmulator(SPIFlashEmulator); 25 | let res <- mkSPIFlashEmulator("boards/qf100/flash.bin"); 26 | return res; 27 | endmodule 28 | 29 | interface CaravelUserProject; 30 | // Logic Analyzer signals 31 | (* always_ready, always_enabled, prefix="" *) 32 | method Action la_in((* port="la_data_in" *) Bit#(128) data 33 | ,(* port="la_oenb" *) Bit#(128) oenb 34 | ); 35 | (* always_ready, result="la_data_out" *) 36 | method Bit#(128) la_out; 37 | 38 | // IOs 39 | (* always_ready, always_enabled, prefix="" *) 40 | method Action io_in((* port="io_in" *) Bit#(38) data 41 | ); 42 | (* always_ready, result="io_out" *) 43 | method Bit#(38) io_out; 44 | (* always_ready, result="io_oeb" *) 45 | method Bit#(38) io_oeb; 46 | 47 | // IRQ 48 | (* always_ready, result="irq" *) 49 | method Bit#(3) irq; 50 | endinterface 51 | 52 | typedef struct { 53 | Bit#(7) unused; 54 | 55 | Bit#(16) gpio; 56 | 57 | Bit#(1) spi_miso; 58 | Bit#(1) spi_mosi; 59 | Bit#(1) spi_sck; 60 | 61 | Bit#(1) mspi_miso; 62 | Bit#(1) mspi_mosi; 63 | Bit#(1) mspi_sck; 64 | Bit#(1) mspi_csb; 65 | 66 | Bit#(8) reserved; 67 | } IOPins deriving (Bits); 68 | 69 | module mkQF105Inner(CaravelUserProject); 70 | let qf100 <- mkQF100; 71 | let sram <- mkSky130SRAM; 72 | 73 | mkConnection(qf100.ram_imem, sram.portB); 74 | mkConnection(qf100.ram_dmem, sram.portA); 75 | 76 | method Bit#(128) la_out = 0; 77 | method Bit#(3) irq = 0; 78 | 79 | method Action io_in(Bit#(38) data); 80 | IOPins v = unpack(data); 81 | 82 | qf100.spi.miso(unpack(v.spi_miso)); 83 | qf100.mspi.miso(unpack(v.mspi_miso)); 84 | qf100.gpio_in(v.gpio); 85 | endmethod 86 | 87 | method Bit#(38) io_oeb; 88 | return ~pack(IOPins { reserved: 0 89 | 90 | , mspi_csb: 1 91 | , mspi_sck: 1 92 | , mspi_mosi: 1 93 | , mspi_miso: 0 94 | 95 | , spi_sck: 1 96 | , spi_mosi: 1 97 | , spi_miso: 0 98 | 99 | , gpio: qf100.gpio_oe() 100 | 101 | , unused: 0 102 | }); 103 | endmethod 104 | 105 | method Bit#(38) io_out; 106 | return pack(IOPins { reserved: 0 107 | 108 | , mspi_csb: pack(qf100.mspi_csb) 109 | , mspi_sck: pack(qf100.mspi.sclk) 110 | , mspi_mosi: qf100.mspi.mosi 111 | , mspi_miso: 0 112 | 113 | , spi_sck: qf100.spi.sclk 114 | , spi_mosi: qf100.spi.mosi 115 | , spi_miso: 0 116 | 117 | , gpio: qf100.gpio_out() 118 | 119 | , unused: 0 120 | }); 121 | endmethod 122 | endmodule 123 | 124 | (* synthesize, default_clock_osc="wb_clk_i", default_reset="wb_rst_i" *) 125 | module mkQF105 (CaravelUserProject); 126 | Reset reset <- exposeCurrentReset(); 127 | Reset reset_n <- mkResetInverter(reset); 128 | 129 | let qf100 <- mkQF105Inner(reset_by reset_n); 130 | return qf100; 131 | endmodule 132 | 133 | 134 | endpackage 135 | -------------------------------------------------------------------------------- /boards/qf100/sky130_sram_2kbyte_1rw1r_32x512_8.v: -------------------------------------------------------------------------------- 1 | // OpenRAM SRAM model 2 | // Words: 512 3 | // Word size: 32 4 | // Write size: 8 5 | 6 | module sky130_sram_2kbyte_1rw1r_32x512_8( 7 | `ifdef USE_POWER_PINS 8 | vccd1, 9 | vssd1, 10 | `endif 11 | // Port 0: RW 12 | clk0,csb0,web0,wmask0,addr0,din0,dout0, 13 | // Port 1: R 14 | clk1,csb1,addr1,dout1 15 | ); 16 | 17 | parameter NUM_WMASKS = 4 ; 18 | parameter DATA_WIDTH = 32 ; 19 | parameter ADDR_WIDTH = 9 ; 20 | parameter RAM_DEPTH = 1 << ADDR_WIDTH; 21 | // FIXME: This delay is arbitrary. 22 | parameter DELAY = 3 ; 23 | parameter VERBOSE = 1 ; //Set to 0 to only display warnings 24 | parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary 25 | 26 | `ifdef USE_POWER_PINS 27 | inout vccd1; 28 | inout vssd1; 29 | `endif 30 | input clk0; // clock 31 | input csb0; // active low chip select 32 | input web0; // active low write control 33 | input [NUM_WMASKS-1:0] wmask0; // write mask 34 | input [ADDR_WIDTH-1:0] addr0; 35 | input [DATA_WIDTH-1:0] din0; 36 | output [DATA_WIDTH-1:0] dout0; 37 | input clk1; // clock 38 | input csb1; // active low chip select 39 | input [ADDR_WIDTH-1:0] addr1; 40 | output [DATA_WIDTH-1:0] dout1; 41 | 42 | reg csb0_reg; 43 | reg web0_reg; 44 | reg [NUM_WMASKS-1:0] wmask0_reg; 45 | reg [ADDR_WIDTH-1:0] addr0_reg; 46 | reg [DATA_WIDTH-1:0] din0_reg; 47 | reg [DATA_WIDTH-1:0] dout0; 48 | 49 | // All inputs are registers 50 | always @(posedge clk0) 51 | begin 52 | csb0_reg = csb0; 53 | web0_reg = web0; 54 | wmask0_reg = wmask0; 55 | addr0_reg = addr0; 56 | din0_reg = din0; 57 | #(T_HOLD) dout0 = 32'bx; 58 | if ( !csb0_reg && web0_reg && VERBOSE ) 59 | $display($time," Reading %m addr0=%b dout0=%b",addr0_reg,mem[addr0_reg]); 60 | if ( !csb0_reg && !web0_reg && VERBOSE ) 61 | $display($time," Writing %m addr0=%b din0=%b wmask0=%b",addr0_reg,din0_reg,wmask0_reg); 62 | end 63 | 64 | reg csb1_reg; 65 | reg [ADDR_WIDTH-1:0] addr1_reg; 66 | reg [DATA_WIDTH-1:0] dout1; 67 | 68 | // All inputs are registers 69 | always @(posedge clk1) 70 | begin 71 | csb1_reg = csb1; 72 | addr1_reg = addr1; 73 | if (!csb0 && !web0 && !csb1 && (addr0 == addr1)) 74 | $display($time," WARNING: Writing and reading addr0=%b and addr1=%b simultaneously!",addr0,addr1); 75 | #(T_HOLD) dout1 = 32'bx; 76 | if ( !csb1_reg && VERBOSE ) 77 | $display($time," Reading %m addr1=%b dout1=%b",addr1_reg,mem[addr1_reg]); 78 | end 79 | 80 | reg [DATA_WIDTH-1:0] mem [0:RAM_DEPTH-1]; 81 | 82 | // Memory Write Block Port 0 83 | // Write Operation : When web0 = 0, csb0 = 0 84 | always @ (negedge clk0) 85 | begin : MEM_WRITE0 86 | if ( !csb0_reg && !web0_reg ) begin 87 | if (wmask0_reg[0]) 88 | mem[addr0_reg][7:0] = din0_reg[7:0]; 89 | if (wmask0_reg[1]) 90 | mem[addr0_reg][15:8] = din0_reg[15:8]; 91 | if (wmask0_reg[2]) 92 | mem[addr0_reg][23:16] = din0_reg[23:16]; 93 | if (wmask0_reg[3]) 94 | mem[addr0_reg][31:24] = din0_reg[31:24]; 95 | end 96 | end 97 | 98 | // Memory Read Block Port 0 99 | // Read Operation : When web0 = 1, csb0 = 0 100 | always @ (negedge clk0) 101 | begin : MEM_READ0 102 | if (!csb0_reg && web0_reg) 103 | dout0 <= #(DELAY) mem[addr0_reg]; 104 | end 105 | 106 | // Memory Read Block Port 1 107 | // Read Operation : When web1 = 1, csb1 = 0 108 | always @ (negedge clk1) 109 | begin : MEM_READ1 110 | if (!csb1_reg) 111 | dout1 <= #(DELAY) mem[addr1_reg]; 112 | end 113 | 114 | endmodule 115 | 116 | module sky130_sram_2kbyte_1rw1r_32x512_8_wrapper( 117 | `ifdef USE_POWER_PINS 118 | vccd1, 119 | vssd1, 120 | `endif 121 | // Port 0: RW 122 | clk0,cs0,web0,wmask0,addr0,din0,dout0, 123 | // Port 1: R 124 | clk1,cs1,addr1,dout1 125 | ); 126 | 127 | parameter NUM_WMASKS = 4 ; 128 | parameter DATA_WIDTH = 32 ; 129 | parameter ADDR_WIDTH = 9 ; 130 | parameter RAM_DEPTH = 1 << ADDR_WIDTH; 131 | // FIXME: This delay is arbitrary. 132 | parameter DELAY = 3 ; 133 | parameter VERBOSE = 1 ; //Set to 0 to only display warnings 134 | parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary 135 | 136 | `ifdef USE_POWER_PINS 137 | inout vccd1; 138 | inout vssd1; 139 | `endif 140 | input clk0; // clock 141 | input cs0; // active high chip select 142 | input web0; // active low write control 143 | input [NUM_WMASKS-1:0] wmask0; // write mask 144 | input [ADDR_WIDTH-1:0] addr0; 145 | input [DATA_WIDTH-1:0] din0; 146 | output [DATA_WIDTH-1:0] dout0; 147 | input clk1; // clock 148 | input cs1; // active high chip select 149 | input [ADDR_WIDTH-1:0] addr1; 150 | output [DATA_WIDTH-1:0] dout1; 151 | sky130_sram_2kbyte_1rw1r_32x512_8 inner( 152 | `ifdef USE_POWER_PINS 153 | .vccd1(vccd1), 154 | .vssd1(vssd1), 155 | `endif 156 | .clk0(clk0), .csb0(!cs0), .web0(web0), .wmask0(wmask0), .addr0(addr0), .din0(din0), .dout0(dout0), 157 | .clk1(clk1), .csb1(!cs1), .addr1(addr1), .dout1(dout1) 158 | ); 159 | 160 | endmodule 161 | -------------------------------------------------------------------------------- /boards/ulx3s/BRAM2Load.v: -------------------------------------------------------------------------------- 1 | // Taken from github.com/B-Lang-org/bsc, file src/Verilog/BRAM2Load.v. 2 | // Modified to remove write enable from second port, allowing Yosys to infer 3 | // that this is not a true dual-ported BRAM. 4 | // 5 | // Copyright (c) 2020 Bluespec, Inc. All rights reserved. 6 | // Redistribution and use in source and binary forms, with or without 7 | // modification, are permitted provided that the following conditions are 8 | // met: 9 | // 10 | // 1. Redistributions of source code must retain the above copyright 11 | // notice, this list of conditions and the following disclaimer. 12 | // 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the 16 | // distribution. 17 | // 18 | // 3. Neither the name of the copyright holder nor the names of its 19 | // contributors may be used to endorse or promote products derived 20 | // from this software without specific prior written permission. 21 | // 22 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | // 34 | // SPDX-License-Identifier: BSD-3-Clause 35 | 36 | `ifdef BSV_ASSIGNMENT_DELAY 37 | `else 38 | `define BSV_ASSIGNMENT_DELAY 39 | `endif 40 | 41 | // Dual-Ported BRAM (WRITE FIRST) 42 | module BRAM2Load(CLKA, 43 | ENA, 44 | WEA, 45 | ADDRA, 46 | DIA, 47 | DOA, 48 | CLKB, 49 | ENB, 50 | WEB, 51 | ADDRB, 52 | DIB, 53 | DOB 54 | ); 55 | 56 | parameter FILENAME = ""; 57 | parameter PIPELINED = 0; 58 | parameter ADDR_WIDTH = 1; 59 | parameter DATA_WIDTH = 1; 60 | parameter MEMSIZE = 1; 61 | parameter BINARY = 0; 62 | 63 | input CLKA; 64 | input ENA; 65 | input WEA; 66 | input [ADDR_WIDTH-1:0] ADDRA; 67 | input [DATA_WIDTH-1:0] DIA; 68 | output [DATA_WIDTH-1:0] DOA; 69 | 70 | input CLKB; 71 | input ENB; 72 | input WEB; 73 | input [ADDR_WIDTH-1:0] ADDRB; 74 | input [DATA_WIDTH-1:0] DIB; 75 | output [DATA_WIDTH-1:0] DOB; 76 | 77 | reg [DATA_WIDTH-1:0] RAM[0:MEMSIZE-1] /* synthesis syn_ramstyle="no_rw_check" */ ; 78 | reg [DATA_WIDTH-1:0] DOA_R; 79 | reg [DATA_WIDTH-1:0] DOB_R; 80 | reg [DATA_WIDTH-1:0] DOA_R2; 81 | reg [DATA_WIDTH-1:0] DOB_R2; 82 | 83 | // synopsys translate_off 84 | initial 85 | begin : init_block 86 | `ifdef BSV_NO_INITIAL_BLOCKS 87 | `else 88 | DOA_R = { ((DATA_WIDTH+1)/2) { 2'b10 } }; 89 | DOB_R = { ((DATA_WIDTH+1)/2) { 2'b10 } }; 90 | DOA_R2 = { ((DATA_WIDTH+1)/2) { 2'b10 } }; 91 | DOB_R2 = { ((DATA_WIDTH+1)/2) { 2'b10 } }; 92 | `endif // !`ifdef BSV_NO_INITIAL_BLOCKS 93 | end 94 | // synopsys translate_on 95 | 96 | initial 97 | begin : init_rom_block 98 | if (BINARY) 99 | $readmemb(FILENAME, RAM, 0, MEMSIZE-1); 100 | else 101 | $readmemh(FILENAME, RAM, 0, MEMSIZE-1); 102 | end 103 | 104 | always @(posedge CLKA) begin 105 | if (ENA) begin 106 | //if (WEA) begin 107 | // RAM[ADDRA] <= `BSV_ASSIGNMENT_DELAY DIA; 108 | // DOA_R <= `BSV_ASSIGNMENT_DELAY DIA; 109 | //end else begin 110 | DOA_R <= `BSV_ASSIGNMENT_DELAY RAM[ADDRA]; 111 | //end 112 | end 113 | DOA_R2 <= `BSV_ASSIGNMENT_DELAY DOA_R; 114 | end 115 | 116 | always @(posedge CLKB) begin 117 | if (ENB) begin 118 | if (WEB) begin 119 | RAM[ADDRB] <= `BSV_ASSIGNMENT_DELAY DIB; 120 | DOB_R <= `BSV_ASSIGNMENT_DELAY DIB; 121 | end 122 | else begin 123 | DOB_R <= `BSV_ASSIGNMENT_DELAY RAM[ADDRB]; 124 | end 125 | end 126 | DOB_R2 <= `BSV_ASSIGNMENT_DELAY DOB_R; 127 | end 128 | 129 | // Output drivers 130 | assign DOA = (PIPELINED) ? DOA_R2 : DOA_R; 131 | assign DOB = (PIPELINED) ? DOB_R2 : DOB_R; 132 | 133 | endmodule // BRAM2Load 134 | -------------------------------------------------------------------------------- /boards/ulx3s/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//build/bluespec:rules.bzl", "bluespec_library") 2 | load("//build/synthesis:rules.bzl", "yosysflow_bitstream") 3 | 4 | bluespec_library( 5 | name = "Board", 6 | srcs = [ 7 | "Board.bsv", 8 | ], 9 | deps = [ 10 | "//fpga:ECP5", 11 | "//systems/qf100", 12 | ], 13 | synthesize = { 14 | "Board": ["mkTop"], 15 | }, 16 | ) 17 | 18 | #genrule( 19 | # name = "bram", 20 | # tools = [ 21 | # "//lanai/qasm", 22 | # ], 23 | # cmd = "$(location //lanai/qasm) $@", 24 | # outs = [ 25 | # "bram.bin" 26 | # ], 27 | #) 28 | 29 | yosysflow_bitstream( 30 | name = 'ulx3s', 31 | deps = [ 32 | ":Board", 33 | ], 34 | srcs = [ 35 | "BRAM2Load.v", 36 | ], 37 | top = "mkTop", 38 | constraints = "ulx3s_v20.lpf", 39 | ) 40 | -------------------------------------------------------------------------------- /boards/ulx3s/Board.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package Board; 5 | 6 | import Connectable :: *; 7 | import TieOff :: *; 8 | 9 | import ECP5 :: *; 10 | import QF100 :: *; 11 | import WishboneSPI :: *; 12 | 13 | interface Top; 14 | (* always_enabled *) 15 | method Bit#(8) led; 16 | 17 | interface ESP32 wifi; 18 | 19 | (* always_enabled *) 20 | method Bool spiMOSI; 21 | 22 | (* always_enabled, always_ready *) 23 | method Action spiMISO(Bool value); 24 | 25 | (* always_enabled *) 26 | method Bool spiSCK; 27 | 28 | (* always_enabled *) 29 | method Bool spiCSB; 30 | endinterface 31 | 32 | interface ESP32; 33 | (* always_enabled *) 34 | method Bit#(1) gpio0; 35 | endinterface 36 | 37 | (* synthesize, default_clock_osc="clk_25mhz", default_reset="btn_pwr" *) 38 | module mkTop (Top); 39 | GSR gsr <- mkGSR; 40 | 41 | let qf100 <- mkQF100; 42 | 43 | rule tieOff; 44 | qf100.gpio_in(0); 45 | qf100.spi.miso(False); 46 | endrule 47 | 48 | interface ESP32 wifi; 49 | method gpio0 = 1; 50 | endinterface 51 | 52 | method led = qf100.gpio_out[7:0]; 53 | method spiMOSI = unpack(qf100.mspi.mosi); 54 | method spiSCK = unpack(qf100.mspi.sclk); 55 | method spiCSB = qf100.mspi_csb; 56 | 57 | method Action spiMISO(Bool value); 58 | qf100.mspi.miso(value); 59 | endmethod 60 | endmodule 61 | 62 | endpackage 63 | -------------------------------------------------------------------------------- /build/BUILD.bazel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q3k/qfc/e13713e483b86f85fb4ec353dfc4f9086096ec7c/build/BUILD.bazel -------------------------------------------------------------------------------- /build/bluespec/BUILD.bazel: -------------------------------------------------------------------------------- 1 | toolchain_type( 2 | name = "toolchain_type", 3 | visibility = ["//visibility:public"], 4 | ) 5 | 6 | cc_binary( 7 | name = "bscwrap", 8 | srcs = [ 9 | "bscwrap.cc", 10 | "subprocess.hpp", 11 | ], 12 | visibility = [ "//visibility:public" ], 13 | ) 14 | 15 | exports_files(["bluetcl.sh", "bsc.sh"]) 16 | -------------------------------------------------------------------------------- /build/bluespec/bluetcl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | # Copyright (C) 2022 Sergiusz Bazanski 5 | 6 | # --- begin runfiles.bash initialization v2 --- 7 | # Copy-pasted from the Bazel Bash runfiles library v2. 8 | set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash 9 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ 10 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ 11 | source "$0.runfiles/$f" 2>/dev/null || \ 12 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 13 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 14 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 15 | # --- end runfiles.bash initialization v2 --- 16 | 17 | exec "$(rlocation bluespec/bin/bluetcl)" "$@" 18 | -------------------------------------------------------------------------------- /build/bluespec/bsc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | # Copyright (C) 2022 Sergiusz Bazanski 5 | 6 | # --- begin runfiles.bash initialization v2 --- 7 | # Copy-pasted from the Bazel Bash runfiles library v2. 8 | set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash 9 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ 10 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ 11 | source "$0.runfiles/$f" 2>/dev/null || \ 12 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 13 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 14 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 15 | # --- end runfiles.bash initialization v2 --- 16 | 17 | exec "$(rlocation bluespec/bin/bsc)" "$@" 18 | -------------------------------------------------------------------------------- /build/bluespec/bscwrap.cc: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | // Set up 'bsc' for execution by giving it a PATH containing a symlink forest 5 | // of executables it wants to shell out to. 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | int main(int argc, char **argv) { 16 | char **new_argv = new char*[argc]; 17 | int new_argv_ix = 0; 18 | 19 | char *bsc = nullptr; 20 | char *cxx = nullptr; 21 | char *strip = nullptr; 22 | 23 | for (int i = 1; i < argc; i++) { 24 | if (new_argv_ix > 0) { 25 | new_argv[new_argv_ix++] = argv[i]; 26 | } else { 27 | if (std::string(argv[i]) == "--") { 28 | new_argv_ix = 1; 29 | } else { 30 | switch (i) { 31 | case 1: 32 | bsc = argv[1]; 33 | break; 34 | case 2: 35 | cxx = argv[i]; 36 | break; 37 | case 3: 38 | strip = argv[i]; 39 | break; 40 | } 41 | } 42 | } 43 | } 44 | if (bsc == nullptr || cxx == nullptr || strip == nullptr) { 45 | std::cerr << "Usage: " << argv[0] << " bsc cxx strip -- bsc args\n"; 46 | return 1; 47 | } 48 | new_argv[0] = bsc; 49 | new_argv[new_argv_ix] = nullptr; 50 | 51 | auto tmp = std::filesystem::current_path(); 52 | auto forest = std::string(tmp) + "/bscwrap"; 53 | 54 | std::filesystem::remove_all(forest); 55 | std::filesystem::create_directory(forest); 56 | 57 | std::filesystem::create_symlink(cxx, forest + "/c++"); 58 | std::filesystem::create_symlink(strip, forest + "/strip"); 59 | 60 | auto path = forest + ":" + (std::getenv("PATH") ? std::getenv("PATH") : ""); 61 | 62 | char *const envp[] = { 63 | strdup((std::string("PATH=") + path).c_str()), 64 | nullptr, 65 | }; 66 | 67 | if (execvpe(bsc, new_argv, envp) == -1) { 68 | std::cerr << "execvpe failed\n"; 69 | return 1; 70 | } 71 | // Unreachable. 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /build/bluespec/rules.bzl: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | load("@qfc//build:providers.bzl", "BscInfo", "VerilogInfo", "BluespecInfo") 5 | load("@rules_cc//cc:toolchain_utils.bzl", "find_cpp_toolchain") 6 | 7 | def _bluespec_toolchain_impl(ctx): 8 | toolchain_info = platform_common.ToolchainInfo( 9 | bscinfo = BscInfo( 10 | bsc = ctx.attr.bsc, 11 | bluetcl = ctx.attr.bluetcl, 12 | verilog_lib = VerilogInfo( 13 | sources = depset(ctx.files.verilog_lib), 14 | ), 15 | ), 16 | ) 17 | return [toolchain_info] 18 | 19 | bluespec_toolchain = rule( 20 | implementation = _bluespec_toolchain_impl, 21 | attrs = { 22 | "bsc": attr.label( 23 | executable = True, 24 | cfg = "exec", 25 | ), 26 | "bluetcl": attr.label( 27 | executable = True, 28 | cfg = "exec", 29 | ), 30 | "verilog_lib": attr.label_list( 31 | allow_files = True, 32 | ), 33 | }, 34 | ) 35 | 36 | def _compile(ctx, src, dep_objs, output, mode, verilog_outputs=[], sim_outputs=[]): 37 | info = ctx.toolchains["@qfc//build/bluespec:toolchain_type"].bscinfo 38 | 39 | bdir = output.dirname 40 | pkg_path_set = {} 41 | for obj in dep_objs.to_list(): 42 | if obj.dirname == bdir: 43 | continue 44 | pkg_path_set[obj.dirname] = True 45 | pkg_path = ['+'] + pkg_path_set.keys() 46 | 47 | arguments = [ 48 | "-bdir", bdir, 49 | "-p", ":".join(pkg_path), 50 | "-aggressive-conditions", 51 | "-q", 52 | ] 53 | if ctx.attr.split_if: 54 | arguments.append("-split-if") 55 | 56 | mnemonic = "" 57 | if mode == "verilog": 58 | mnemonic = "BluespecVerilogCompile" 59 | vdir = bdir 60 | if len(verilog_outputs) > 0: 61 | vdir = verilog_outputs[0].dirname 62 | arguments += [ 63 | "-verilog", 64 | "-vdir", vdir, 65 | ] 66 | elif mode == "sim": 67 | mnemonic = "BluespecSimCompile" 68 | simdir = bdir 69 | arguments += [ 70 | "-sim", 71 | "-check-assert", 72 | ] 73 | else: 74 | fail("Invalid mode {}".format(mode)) 75 | 76 | arguments += [ 77 | src.path, 78 | ] 79 | 80 | _, _, input_manifests = ctx.resolve_command(tools = [info.bsc]) 81 | ctx.actions.run( 82 | mnemonic = mnemonic, 83 | executable = info.bsc.files_to_run, 84 | arguments = arguments, 85 | inputs = depset([ src ], transitive=[dep_objs]), 86 | outputs = sim_outputs + verilog_outputs + [ output ], 87 | input_manifests = input_manifests, 88 | use_default_shell_env = True, 89 | env = { 90 | "PATH": ctx.configuration.default_shell_env.get('PATH', ""), 91 | }, 92 | ) 93 | 94 | 95 | def _library_inner(ctx): 96 | info = ctx.toolchains["@qfc//build/bluespec:toolchain_type"].bscinfo 97 | 98 | package_order = [] 99 | package_to_src = {} 100 | package_to_sim_obj = {} 101 | package_to_verilog_obj = {} 102 | package_to_verilog_modules = {} 103 | package_to_sim_modules = {} 104 | 105 | for src in ctx.files.srcs: 106 | basename = src.basename 107 | if not basename.endswith(".bsv"): 108 | fail("Source {} invalid: does not end in .bsv".format(basename)) 109 | package = basename[:-4] 110 | 111 | package_order.append(package) 112 | package_to_src[package] = src 113 | sim_obj_name = "{}.sim/{}.bo".format(ctx.attr.name, package) 114 | verilog_obj_name = "{}.verilog/{}.bo".format(ctx.attr.name, package) 115 | package_to_sim_obj[package] = ctx.actions.declare_file(sim_obj_name) 116 | package_to_verilog_obj[package] = ctx.actions.declare_file(verilog_obj_name) 117 | 118 | 119 | for package, modules in ctx.attr.synthesize.items(): 120 | if package not in package_to_src: 121 | fail("Package {} (in synthesize) does not exist in srcs".format(package)) 122 | package_to_verilog_modules[package] = [] 123 | package_to_sim_modules[package] = [] 124 | for module in modules: 125 | verilog_name = "{}/{}.v".format(package, module) 126 | verilog = ctx.actions.declare_file(verilog_name) 127 | package_to_verilog_modules[package].append(verilog) 128 | sim_name = "{}.sim/{}.ba".format(ctx.attr.name, module) 129 | sim = ctx.actions.declare_file(sim_name) 130 | package_to_sim_modules[package].append(sim) 131 | 132 | 133 | input_sim_objects = depset(direct=[], transitive=[ 134 | dep[BluespecInfo].sim_objects 135 | for dep in ctx.attr.deps 136 | ]) 137 | input_verilog_objects = depset(direct=[], transitive=[ 138 | dep[BluespecInfo].verilog_objects 139 | for dep in ctx.attr.deps 140 | ]) 141 | data_files = depset( 142 | direct = [], 143 | transitive = [ 144 | d.files 145 | for d in ctx.attr.data 146 | ] + [ 147 | dep[BluespecInfo].data_files 148 | for dep in ctx.attr.deps 149 | ], 150 | ) 151 | 152 | cur_sim_objs = input_sim_objects 153 | cur_verilog_objs = input_verilog_objects 154 | 155 | for package in package_order: 156 | src = package_to_src[package] 157 | sim_obj = package_to_sim_obj[package] 158 | verilog_obj = package_to_verilog_obj[package] 159 | verilog_modules = package_to_verilog_modules.get(package, []) 160 | sim_modules = package_to_sim_modules.get(package, []) 161 | 162 | _compile( 163 | ctx = ctx, 164 | src = src, 165 | dep_objs = cur_sim_objs, 166 | output = sim_obj, 167 | mode = 'sim', 168 | sim_outputs = sim_modules, 169 | ) 170 | _compile( 171 | ctx = ctx, 172 | src = src, 173 | dep_objs = cur_verilog_objs, 174 | output = verilog_obj, 175 | mode = 'verilog', 176 | verilog_outputs = verilog_modules, 177 | ) 178 | 179 | cur_sim_objs = depset([ sim_obj ], transitive=[cur_sim_objs]) 180 | cur_verilog_objs = depset([ verilog_obj ], transitive=[cur_verilog_objs]) 181 | 182 | 183 | verilog_modules = [] 184 | for modules in package_to_verilog_modules.values(): 185 | verilog_modules += modules 186 | sim_modules = [] 187 | for modules in package_to_sim_modules.values(): 188 | sim_modules += modules 189 | 190 | return struct( 191 | sim_objs = package_to_sim_obj.values(), 192 | sim_objs_deps = input_sim_objects, 193 | verilog_objs = package_to_verilog_obj.values(), 194 | verilog_objs_deps = input_verilog_objects, 195 | verilog_modules = verilog_modules, 196 | verilog_modules_deps = depset( 197 | transitive = [dep[VerilogInfo].sources for dep in ctx.attr.deps]+ [ 198 | info.verilog_lib.sources, 199 | ], 200 | ), 201 | sim_modules = sim_modules, 202 | sim_modules_deps = depset( 203 | [], 204 | transitive = [dep[BluespecInfo].sim_outputs for dep in ctx.attr.deps], 205 | ), 206 | data_files = data_files, 207 | ) 208 | 209 | 210 | def _bluespec_library_impl(ctx): 211 | compiled = _library_inner(ctx) 212 | 213 | return [ 214 | DefaultInfo( 215 | files=depset( 216 | compiled.sim_objs + 217 | compiled.verilog_objs + 218 | compiled.verilog_modules 219 | ) 220 | ), 221 | BluespecInfo( 222 | sim_objects = depset( 223 | compiled.sim_objs, 224 | transitive = [ compiled.sim_objs_deps ], 225 | ), 226 | verilog_objects = depset( 227 | compiled.verilog_objs, 228 | transitive = [ compiled.verilog_objs_deps ], 229 | ), 230 | sim_outputs = depset( 231 | compiled.sim_modules, 232 | transitive = [ compiled.sim_modules_deps ], 233 | ), 234 | data_files = compiled.data_files, 235 | ), 236 | VerilogInfo( 237 | sources = depset( 238 | compiled.verilog_modules, 239 | transitive = [ compiled.verilog_modules_deps ], 240 | ), 241 | data_files = compiled.data_files, 242 | ), 243 | ] 244 | 245 | bluespec_library = rule( 246 | implementation = _bluespec_library_impl, 247 | attrs = { 248 | "srcs": attr.label_list(allow_files = True), 249 | "deps": attr.label_list( 250 | providers = [BluespecInfo, VerilogInfo], 251 | ), 252 | "data": attr.label_list( 253 | allow_files = True, 254 | ), 255 | "synthesize": attr.string_list_dict(), 256 | "split_if": attr.bool(), 257 | }, 258 | toolchains = ["@qfc//build/bluespec:toolchain_type"] 259 | ) 260 | 261 | 262 | def _bluesim_test_impl(ctx): 263 | info = ctx.toolchains["@qfc//build/bluespec:toolchain_type"].bscinfo 264 | cc_toolchain = find_cpp_toolchain(ctx) 265 | bsc = info.bsc 266 | bluetcl = info.bluetcl 267 | 268 | sim_objs = depset( 269 | [], 270 | transitive = [dep[BluespecInfo].sim_objects for dep in ctx.attr.deps], 271 | ) 272 | sim_outputs = depset( 273 | [], 274 | transitive = [dep[BluespecInfo].sim_outputs for dep in ctx.attr.deps], 275 | ) 276 | data_files = depset( 277 | [], 278 | transitive = [ 279 | d.files 280 | for d in ctx.attr.data 281 | ] + [ 282 | dep[BluespecInfo].data_files 283 | for dep in ctx.attr.deps 284 | ] + [ bluetcl.default_runfiles.files ], 285 | ) 286 | 287 | pkg_path_set = {} 288 | for obj in sim_objs.to_list() + sim_outputs.to_list(): 289 | pkg_path_set[obj.dirname] = True 290 | pkg_path = ['+'] + pkg_path_set.keys() 291 | 292 | test = ctx.actions.declare_file(ctx.attr.name) 293 | testSo = ctx.actions.declare_file(ctx.attr.name + ".so") 294 | 295 | cxx = cc_toolchain.compiler_executable 296 | # HACK: if we use foo/gcc, use foo/g++ instead. This is needed because bsc 297 | # uses the compiler to link C++ code, and gcc-as-a-linker does not -lstdc++ 298 | # by default, while g++-as-a-linker does. 299 | # The proper way to fix this would be to better pipe cc_toolchain 300 | # information into bsc somehow. Maybe using a real linker? 301 | if cxx.endswith('/gcc'): 302 | cxx = cxx[:-2] + "++" 303 | elif cxx.endswith('/cc'): 304 | cxx = cxx[:-2] + "g++" 305 | 306 | ctx.actions.run( 307 | mnemonic = "BluespecSimLink", 308 | executable = ctx.executable._bscwrap, 309 | tools = [ 310 | bsc.files_to_run 311 | ], 312 | inputs = depset( 313 | [], 314 | transitive = [ sim_objs, sim_outputs ], 315 | ), 316 | arguments = [ 317 | bsc.files_to_run.executable.path, cxx, cc_toolchain.strip_executable, 318 | "--", 319 | "-p", ":".join(pkg_path), 320 | "-sim", 321 | "-simdir", test.dirname, 322 | "-o", test.path, 323 | "-e", 324 | ctx.attr.top, 325 | ], 326 | use_default_shell_env = True, 327 | outputs = [test, testSo] 328 | ) 329 | 330 | wrapper = ctx.actions.declare_file(ctx.attr.name + ".wrap") 331 | test_path = ctx.workspace_name + "/" + test.short_path 332 | ctx.actions.write( 333 | output = wrapper, 334 | content = """#!/usr/bin/env bash 335 | # --- begin runfiles.bash initialization v2 --- 336 | # Copy-pasted from the Bazel Bash runfiles library v2. 337 | set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash 338 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \\ 339 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \\ 340 | source "$0.runfiles/$f" 2>/dev/null || \\ 341 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \\ 342 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \\ 343 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 344 | # --- end runfiles.bash initialization v2 --- """ + (""" 345 | export PATH="$(dirname $(rlocation bluespec/bluetcl)):$PATH" 346 | t="$(rlocation {})" 347 | $t $@ >res 2>&1 348 | cat res 349 | if grep -q "Error:" res ; then 350 | exit 1 351 | fi 352 | if grep -q "Dynamic assertion failed:" res ; then 353 | exit 1 354 | fi 355 | """.format(test_path)), 356 | is_executable = True, 357 | ) 358 | 359 | return [ 360 | DefaultInfo( 361 | executable = wrapper, 362 | runfiles = ctx.runfiles( 363 | files = [ wrapper, test, testSo ], 364 | transitive_files = data_files, 365 | ), 366 | ), 367 | ] 368 | 369 | bluesim_test = rule( 370 | implementation = _bluesim_test_impl, 371 | attrs = { 372 | "srcs": attr.label_list(allow_files = True), 373 | "deps": attr.label_list( 374 | providers = [BluespecInfo], 375 | ), 376 | "data": attr.label_list(allow_files = True), 377 | "top": attr.string(), 378 | 379 | "_bscwrap": attr.label( 380 | default = Label("@qfc//build/bluespec:bscwrap"), 381 | executable = True, 382 | cfg = "exec", 383 | ), 384 | "_cc_toolchain": attr.label( 385 | default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), 386 | ), 387 | }, 388 | test = True, 389 | toolchains = [ 390 | "@qfc//build/bluespec:toolchain_type", 391 | ], 392 | ) 393 | -------------------------------------------------------------------------------- /build/bluespec/subprocess.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // subprocess C++ library - https://github.com/tsaarni/cpp-subprocess 3 | // 4 | // The MIT License (MIT) 5 | // 6 | // Copyright (c) 2015 Tero Saarni 7 | // Copyright (c) 2021 Sergiusz Bazanski 8 | // 9 | // SPDX-License-Identifier: MIT 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | namespace subprocess 25 | { 26 | 27 | class popen 28 | { 29 | public: 30 | 31 | popen(const std::string& cmd, std::vector argv, std::vector env) 32 | : in_filebuf(nullptr), out_filebuf(nullptr), err_filebuf(nullptr), in_stream(nullptr), out_stream(nullptr), err_stream(nullptr) 33 | { 34 | if (pipe(in_pipe) == -1 || 35 | pipe(out_pipe) == -1 || 36 | pipe(err_pipe) == -1 ) 37 | { 38 | throw std::system_error(errno, std::system_category()); 39 | } 40 | 41 | run(cmd, argv, env); 42 | } 43 | 44 | popen(const std::string& cmd, std::vector argv, std::vector env, std::ostream& pipe_stdout) 45 | : in_filebuf(nullptr), out_filebuf(nullptr), err_filebuf(nullptr), in_stream(nullptr), out_stream(nullptr), err_stream(nullptr) 46 | { 47 | auto filebuf = dynamic_cast<__gnu_cxx::stdio_filebuf*>(pipe_stdout.rdbuf()); 48 | out_pipe[READ] = -1; 49 | out_pipe[WRITE] = filebuf->fd(); 50 | 51 | if (pipe(in_pipe) == -1 || 52 | pipe(err_pipe) == -1 ) 53 | { 54 | throw std::system_error(errno, std::system_category()); 55 | } 56 | 57 | run(cmd, argv, env); 58 | } 59 | 60 | ~popen() 61 | { 62 | delete in_filebuf; 63 | delete in_stream; 64 | if (out_filebuf != nullptr) delete out_filebuf; 65 | if (out_stream != nullptr) delete out_stream; 66 | delete err_filebuf; 67 | delete err_stream; 68 | } 69 | 70 | std::ostream& stdin() { return *in_stream; }; 71 | 72 | std::istream& stdout() 73 | { 74 | if (out_stream == nullptr) throw std::system_error(EBADF, std::system_category()); 75 | return *out_stream; 76 | }; 77 | 78 | std::istream& stderr() { return *err_stream; }; 79 | 80 | int wait() 81 | { 82 | int status = 0; 83 | waitpid(pid, &status, 0); 84 | return WEXITSTATUS(status); 85 | }; 86 | 87 | void close() 88 | { 89 | in_filebuf->close(); 90 | } 91 | 92 | 93 | private: 94 | 95 | enum ends_of_pipe { READ = 0, WRITE = 1 }; 96 | 97 | struct raii_char_str 98 | { 99 | raii_char_str(std::string s) : buf(s.c_str(), s.c_str() + s.size() + 1) { }; 100 | operator char*() const { return &buf[0]; }; 101 | mutable std::vector buf; 102 | }; 103 | 104 | void run(const std::string& cmd, std::vector argv, std::vector env) 105 | { 106 | argv.insert(argv.begin(), cmd); 107 | 108 | pid = ::fork(); 109 | 110 | if (pid == 0) child(argv, env); 111 | 112 | ::close(in_pipe[READ]); 113 | ::close(out_pipe[WRITE]); 114 | ::close(err_pipe[WRITE]); 115 | 116 | in_filebuf = new __gnu_cxx::stdio_filebuf(in_pipe[WRITE], std::ios_base::out, 1); 117 | in_stream = new std::ostream(in_filebuf); 118 | 119 | if (out_pipe[READ] != -1) 120 | { 121 | out_filebuf = new __gnu_cxx::stdio_filebuf(out_pipe[READ], std::ios_base::in, 1); 122 | out_stream = new std::istream(out_filebuf); 123 | } 124 | 125 | err_filebuf = new __gnu_cxx::stdio_filebuf(err_pipe[READ], std::ios_base::in, 1); 126 | err_stream = new std::istream(err_filebuf); 127 | } 128 | 129 | void child(const std::vector& argv, const std::vector& env) 130 | { 131 | if (dup2(in_pipe[READ], STDIN_FILENO) == -1 || 132 | dup2(out_pipe[WRITE], STDOUT_FILENO) == -1 || 133 | dup2(err_pipe[WRITE], STDERR_FILENO) == -1 ) 134 | { 135 | std::perror("subprocess: dup2() failed"); 136 | return; 137 | } 138 | 139 | ::close(in_pipe[READ]); 140 | ::close(in_pipe[WRITE]); 141 | if (out_pipe[READ] != -1) ::close(out_pipe[READ]); 142 | ::close(out_pipe[WRITE]); 143 | ::close(err_pipe[READ]); 144 | ::close(err_pipe[WRITE]); 145 | 146 | std::vector real_args(argv.begin(), argv.end()); 147 | std::vector cargs(real_args.begin(), real_args.end()); 148 | cargs.push_back(nullptr); 149 | 150 | std::vector real_env(env.begin(), env.end()); 151 | std::vector cenv(real_env.begin(), real_env.end()); 152 | cenv.push_back(nullptr); 153 | 154 | if (execvpe(cargs[0], &cargs[0], &cenv[0]) == -1) 155 | { 156 | std::perror("subprocess: execvp() failed"); 157 | return; 158 | } 159 | } 160 | 161 | pid_t pid; 162 | 163 | int in_pipe[2]; 164 | int out_pipe[2]; 165 | int err_pipe[2]; 166 | 167 | __gnu_cxx::stdio_filebuf* in_filebuf; 168 | __gnu_cxx::stdio_filebuf* out_filebuf; 169 | __gnu_cxx::stdio_filebuf* err_filebuf; 170 | 171 | std::ostream* in_stream; 172 | std::istream* out_stream; 173 | std::istream* err_stream; 174 | }; 175 | 176 | } // namespace: subprocess 177 | -------------------------------------------------------------------------------- /build/platforms/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package( 2 | default_visibility = ["//visibility:public"], 3 | ) 4 | 5 | constraint_setting(name = "fpga_family") 6 | 7 | constraint_value( 8 | name = "ecp5", 9 | constraint_setting = ":fpga_family", 10 | ) 11 | 12 | constraint_value( 13 | name = "ice40", 14 | constraint_setting = ":fpga_family", 15 | ) 16 | 17 | constraint_setting(name = "ecp5_device_type") 18 | 19 | constraint_value( 20 | name = "LFE5U_12F", 21 | constraint_setting = ":ecp5_device_type", 22 | ) 23 | 24 | constraint_value( 25 | name = "LFE5U_25F", 26 | constraint_setting = ":ecp5_device_type", 27 | ) 28 | 29 | constraint_value( 30 | name = "LFE5U_85F", 31 | constraint_setting = ":ecp5_device_type", 32 | ) 33 | 34 | constraint_setting(name = "ecp5_package") 35 | 36 | constraint_value( 37 | name = "CABGA381", 38 | constraint_setting = ":ecp5_package", 39 | ) 40 | 41 | constraint_value( 42 | name = "CABGA256", 43 | constraint_setting = ":ecp5_package", 44 | ) 45 | 46 | constraint_setting(name = "board") 47 | 48 | constraint_value( 49 | name = "ulx3s", 50 | constraint_setting = ":board", 51 | ) 52 | 53 | constraint_value( 54 | name = "colorlight", 55 | constraint_setting = ":board", 56 | ) 57 | 58 | platform( 59 | name = "ulx3s_12f", 60 | constraint_values = [ 61 | "@platforms//os:none", 62 | ":ecp5", 63 | ":LFE5U_12F", 64 | ":CABGA381", 65 | ":ulx3s", 66 | ], 67 | ) 68 | 69 | platform( 70 | name = "ulx3s_85f", 71 | constraint_values = [ 72 | "@platforms//os:none", 73 | ":ecp5", 74 | ":LFE5U_85F", 75 | ":CABGA381", 76 | ":ulx3s", 77 | ], 78 | ) 79 | 80 | platform( 81 | name = "colorlight_70", 82 | constraint_values = [ 83 | "@platforms//os:none", 84 | ":ecp5", 85 | ":LFE5U_25F", 86 | ":CABGA256", 87 | ":colorlight", 88 | ], 89 | ) 90 | -------------------------------------------------------------------------------- /build/providers.bzl: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | # Bluespec Compiler provider, used by Bluespec Toolchain. 5 | BscInfo = provider( 6 | fields = [ 7 | "bsc", 8 | "bluetcl", 9 | "verilog_lib", 10 | ], 11 | ) 12 | 13 | YosysFlowInfo = provider( 14 | fields = [ 15 | "yosys", 16 | "nextpnr", 17 | "packer", 18 | 19 | "synth_command", 20 | ], 21 | ) 22 | 23 | # Bluespec intermediary compilation data. 24 | BluespecInfo = provider( 25 | fields = [ 26 | "sim_objects", 27 | "verilog_objects", 28 | "sim_outputs", 29 | "data_files", 30 | ], 31 | ) 32 | 33 | # Verilog module sources. 34 | VerilogInfo = provider( 35 | fields = [ 36 | "sources", 37 | "data_files", 38 | ], 39 | ) 40 | 41 | -------------------------------------------------------------------------------- /build/synthesis/BUILD.bazel: -------------------------------------------------------------------------------- 1 | toolchain_type( 2 | name = "toolchain_type", 3 | visibility = ["//visibility:public"], 4 | ) 5 | 6 | py_binary( 7 | name = "add_power_pins", 8 | srcs = [ 9 | ":add_power_pins.py", 10 | ], 11 | visibility = ["//visibility:public"], 12 | ) 13 | -------------------------------------------------------------------------------- /build/synthesis/add_power_pins.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | import sys 5 | 6 | name = sys.argv[1] 7 | blackboxes = [b for b in sys.argv[2].strip().split(',') if b] 8 | 9 | with open(sys.argv[3], "r") as f: 10 | data = f.read() 11 | with open(sys.argv[4], "w") as f: 12 | declaration_prefix = f"module {name}(" 13 | 14 | in_declaration = False 15 | for line in data.split("\n"): 16 | if in_declaration: 17 | if line.startswith(','): 18 | f.write(line + "\n") 19 | else: 20 | in_declaration = False 21 | f.write("`ifdef USE_POWER_PINS\n") 22 | f.write(" inout vccd1;\n") 23 | f.write(" inout vssd1;\n") 24 | f.write("`endif\n") 25 | f.write(line + "\n") 26 | else: 27 | if line.startswith(declaration_prefix): 28 | f.write(f"module {name}(\n") 29 | f.write("`ifdef USE_POWER_PINS\n") 30 | f.write(" vccd1,\n") 31 | f.write(" vssd1,\n") 32 | f.write("`endif\n") 33 | f.write(f" " + line[len(declaration_prefix):] + "\n") 34 | in_declaration = True 35 | else: 36 | in_instantiation = False 37 | for blackbox in blackboxes: 38 | if line.strip().startswith(blackbox + ' '): 39 | assert line.endswith('(') 40 | in_instantiation = True 41 | break 42 | 43 | f.write(line + "\n") 44 | if in_instantiation: 45 | f.write("`ifdef USE_POWER_PINS\n") 46 | f.write(" .vccd1(vccd1),\n") 47 | f.write(" .vssd1(vssd1),\n") 48 | f.write("`endif\n") 49 | 50 | -------------------------------------------------------------------------------- /build/synthesis/rules.bzl: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | load("@qfc//build:providers.bzl", "YosysFlowInfo", "VerilogInfo") 5 | 6 | def _yosysflow_toolchain_impl(ctx): 7 | toolchain_info = platform_common.ToolchainInfo( 8 | yosysflowinfo = YosysFlowInfo( 9 | yosys = ctx.attr.yosys, 10 | nextpnr = ctx.attr.nextpnr, 11 | packer = ctx.attr.packer, 12 | ), 13 | ) 14 | return [toolchain_info] 15 | 16 | 17 | yosysflow_toolchain = rule( 18 | implementation = _yosysflow_toolchain_impl, 19 | attrs = { 20 | "yosys": attr.label( 21 | executable = True, 22 | cfg = "exec", 23 | ), 24 | "nextpnr": attr.label( 25 | executable = True, 26 | cfg = "exec", 27 | ), 28 | "packer": attr.label( 29 | executable = True, 30 | cfg = "exec", 31 | ), 32 | }, 33 | ) 34 | 35 | def _is_set(ctx, attr): 36 | constraint = attr[platform_common.ConstraintValueInfo] 37 | return ctx.target_platform_has_constraint(constraint) 38 | 39 | def _get_flags(ctx): 40 | if _is_set(ctx, ctx.attr._fpga_family_ecp5): 41 | nextpnr_flags = [ 42 | "--lpf", "%CONSTRAINT_FILE%", 43 | "--json", "%JSON_FILE%", 44 | "--textcfg", "%OUT_FILE%", 45 | ] 46 | if _is_set(ctx, ctx.attr._ecp5_lfe5u_12f): 47 | nextpnr_flags.append("--12k") 48 | if _is_set(ctx, ctx.attr._ecp5_lfe5u_25f): 49 | nextpnr_flags.append("--25k") 50 | if _is_set(ctx, ctx.attr._ecp5_lfe5u_85f): 51 | nextpnr_flags.append("--85k") 52 | if _is_set(ctx, ctx.attr._ecp5_cabga381): 53 | nextpnr_flags += ["--package", "CABGA381"] 54 | if _is_set(ctx, ctx.attr._ecp5_cabga256): 55 | nextpnr_flags += ["--package", "CABGA256"] 56 | 57 | return struct( 58 | yosys_synth_command = "synth_ecp5 -abc9 -top %TOP%", 59 | nextpnr_flags = nextpnr_flags, 60 | ) 61 | fail("Unsupported FPGA (needs //build/platforms:fpga_family constraint set)") 62 | 63 | def _yosysflow_bitstream_impl(ctx): 64 | info = ctx.toolchains["@qfc//build/synthesis:toolchain_type"].yosysflowinfo 65 | yosys = info.yosys[DefaultInfo].files_to_run 66 | nextpnr = info.nextpnr[DefaultInfo].files_to_run 67 | packer = info.packer[DefaultInfo].files_to_run 68 | flags = _get_flags(ctx) 69 | 70 | sources = depset( 71 | ctx.files.srcs, 72 | transitive = [dep[VerilogInfo].sources for dep in ctx.attr.deps], 73 | ) 74 | data_files = depset( 75 | [], 76 | transitive = [dep[VerilogInfo].data_files for dep in ctx.attr.deps], 77 | ) 78 | 79 | srcline = " ".join([s.path for s in sources.to_list()]) 80 | synth_command = flags.yosys_synth_command.replace('%TOP%', ctx.attr.top) 81 | 82 | json = ctx.actions.declare_file(ctx.attr.name + ".json") 83 | scriptfile = ctx.actions.declare_file(ctx.attr.name + ".ys") 84 | ctx.actions.write( 85 | output = scriptfile, 86 | content = """ 87 | read_verilog -defer {} 88 | {} 89 | write_json {} 90 | """.format( 91 | srcline, 92 | synth_command, 93 | json.path, 94 | ), 95 | ) 96 | 97 | ctx.actions.run( 98 | mnemonic = "DesignSynthesize", 99 | executable = yosys, 100 | arguments = [ 101 | "-s", scriptfile.path, 102 | ], 103 | inputs = depset([ scriptfile ], transitive=[ sources, data_files ]), 104 | outputs = [ json ], 105 | ) 106 | 107 | unpacked = ctx.actions.declare_file(ctx.attr.name + ".pnr") 108 | 109 | nextpnr_arguments = [ 110 | (f 111 | .replace("%CONSTRAINT_FILE%", ctx.file.constraints.path) 112 | .replace("%JSON_FILE%", json.path) 113 | .replace("%OUT_FILE%", unpacked.path) 114 | ) 115 | for f in flags.nextpnr_flags 116 | ] 117 | 118 | ctx.actions.run( 119 | mnemonic = "BitstreamRoute", 120 | executable = nextpnr, 121 | arguments = nextpnr_arguments + [ ], 122 | inputs = [ json, ctx.file.constraints ], 123 | outputs = [ unpacked ], 124 | ) 125 | 126 | packed = ctx.actions.declare_file(ctx.attr.name + ".bit") 127 | svf = ctx.actions.declare_file(ctx.attr.name + ".svf") 128 | 129 | ctx.actions.run( 130 | mnemonic = "BitstreamPack", 131 | executable = packer, 132 | arguments = [ unpacked.path, packed.path, "--svf", svf.path ], 133 | inputs = [ unpacked ], 134 | outputs = [ packed, svf ], 135 | ) 136 | 137 | return [ 138 | DefaultInfo( 139 | files = depset([packed, svf]), 140 | ) 141 | ] 142 | 143 | yosysflow_bitstream = rule( 144 | implementation = _yosysflow_bitstream_impl, 145 | attrs = { 146 | "srcs": attr.label_list( 147 | allow_files = True, 148 | ), 149 | "deps": attr.label_list( 150 | providers = [VerilogInfo], 151 | ), 152 | "top": attr.string(), 153 | "constraints": attr.label(allow_single_file = True), 154 | 155 | "_fpga_family_ecp5": attr.label(default="@qfc//build/platforms:ecp5"), 156 | "_fpga_family_ice40": attr.label(default="@qfc//build/platforms:ice40"), 157 | "_ecp5_lfe5u_12f": attr.label(default="@qfc//build/platforms:LFE5U_12F"), 158 | "_ecp5_lfe5u_25f": attr.label(default="@qfc//build/platforms:LFE5U_25F"), 159 | "_ecp5_lfe5u_85f": attr.label(default="@qfc//build/platforms:LFE5U_85F"), 160 | "_ecp5_cabga381": attr.label(default="@qfc//build/platforms:CABGA381"), 161 | "_ecp5_cabga256": attr.label(default="@qfc//build/platforms:CABGA256"), 162 | }, 163 | toolchains = ["@qfc//build/synthesis:toolchain_type"], 164 | ) 165 | 166 | def _rtl_bundle_impl(ctx): 167 | sources = depset( 168 | ctx.files.srcs, 169 | transitive = [dep[VerilogInfo].sources for dep in ctx.attr.deps], 170 | ) 171 | data_files = depset( 172 | [], 173 | transitive = [dep[VerilogInfo].data_files for dep in ctx.attr.deps], 174 | ) 175 | 176 | info = ctx.toolchains["@qfc//build/synthesis:toolchain_type"].yosysflowinfo 177 | yosys = info.yosys[DefaultInfo].files_to_run 178 | 179 | bundle = ctx.actions.declare_file(ctx.label.name + ".bundle.zip") 180 | 181 | args = ["c", bundle.path] 182 | for f in sources.to_list() + data_files.to_list(): 183 | args.append(f.path) 184 | 185 | ctx.actions.run( 186 | inputs = sources.to_list() + data_files.to_list(), 187 | outputs = [bundle], 188 | executable = ctx.executable._zipper, 189 | arguments = args, 190 | progress_message = "Creating bundle...", 191 | mnemonic = "zipper", 192 | ) 193 | 194 | outputs = [] 195 | for (name, blackboxes) in ctx.attr.outputs.items(): 196 | srcline = " ".join([s.path for s in sources.to_list()]) 197 | intermediate = ctx.actions.declare_file(ctx.label.name + "_/" + name + ".intermediate.v") 198 | presynth = ctx.actions.declare_file(ctx.label.name + "/" + name + ".v") 199 | scriptfile = ctx.actions.declare_file(ctx.attr.name + "_/" + name + ".ys") 200 | content = """ 201 | read_verilog -defer {} 202 | hierarchy -top {} -purge_lib 203 | """.format(srcline, name) 204 | 205 | for blackbox in blackboxes: 206 | content += "blackbox {}\n".format(blackbox) 207 | 208 | content += """ 209 | hierarchy -top {} -purge_lib 210 | write_verilog {} 211 | """.format( 212 | name, 213 | intermediate.path, 214 | ) 215 | 216 | ctx.actions.write( 217 | output = scriptfile, 218 | content = content 219 | ) 220 | 221 | ctx.actions.run( 222 | mnemonic = "DesignPresynth", 223 | executable = yosys, 224 | arguments = [ 225 | "-s", scriptfile.path, 226 | "-q", 227 | ], 228 | inputs = depset([ scriptfile ], transitive=[ sources, data_files ]), 229 | outputs = [intermediate], 230 | ) 231 | 232 | ctx.actions.run( 233 | mnemonic = "PowerPinsAdd", 234 | executable = ctx.executable._add_power_pins, 235 | arguments = [ 236 | name, 237 | ','.join(blackboxes), 238 | intermediate.path, 239 | presynth.path, 240 | ], 241 | inputs = [ intermediate ], 242 | outputs = [ presynth ], 243 | ) 244 | 245 | outputs.append(presynth) 246 | 247 | return [ 248 | DefaultInfo( 249 | files = depset([bundle]+outputs), 250 | ) 251 | ] 252 | 253 | rtl_bundle = rule( 254 | implementation = _rtl_bundle_impl, 255 | attrs = { 256 | "srcs": attr.label_list( 257 | allow_files = True, 258 | ), 259 | "deps": attr.label_list( 260 | providers = [VerilogInfo], 261 | ), 262 | "outputs": attr.string_list_dict(), 263 | "_add_power_pins": attr.label( 264 | default = Label("//build/synthesis:add_power_pins"), 265 | cfg = "host", 266 | executable = True, 267 | ), 268 | "_zipper": attr.label( 269 | default = Label("@bazel_tools//tools/zip:zipper"), 270 | cfg = "host", 271 | executable = True, 272 | ), 273 | }, 274 | toolchains = ["@qfc//build/synthesis:toolchain_type"], 275 | ) 276 | -------------------------------------------------------------------------------- /build/utils.bzl: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | def _external_binary_tool_impl(ctx): 5 | bin_ = ctx.file.bin 6 | 7 | executable = ctx.actions.declare_file(ctx.attr.name + ".bin") 8 | path = executable.path + ".runfiles/" + ctx.workspace_name + "/" + ctx.file.bin.short_path 9 | ctx.actions.write( 10 | output = executable, 11 | content = """#!/bin/sh 12 | exec "{}" $@ 13 | """.format(path), 14 | is_executable = True, 15 | ) 16 | 17 | files = depset([executable]) 18 | runfiles = ctx.runfiles(files = [ctx.file.bin, executable] + ctx.files.deps) 19 | res = [ 20 | DefaultInfo(files=files, runfiles=runfiles, executable=executable), 21 | ] 22 | return res 23 | 24 | external_binary_tool = rule( 25 | implementation = _external_binary_tool_impl, 26 | attrs = { 27 | "bin": attr.label( 28 | executable = True, 29 | allow_single_file = True, 30 | cfg = "exec", 31 | ), 32 | "deps": attr.label_list( 33 | allow_files = True, 34 | ), 35 | }, 36 | ) 37 | 38 | -------------------------------------------------------------------------------- /fpga/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//build/bluespec:rules.bzl", "bluespec_library") 2 | 3 | bluespec_library( 4 | name = "ECP5", 5 | srcs = [ 6 | "ECP5.bsv", 7 | ], 8 | visibility = ["//visibility:public"], 9 | ) 10 | -------------------------------------------------------------------------------- /fpga/ECP5.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package ECP5; 5 | 6 | interface GSR; 7 | endinterface 8 | 9 | import "BVI" GSR = module mkGSR(GSR ifc); 10 | default_clock no_clock; 11 | default_reset gsr (GSR); 12 | endmodule 13 | 14 | interface EHXPLL; 15 | interface Clock clkop; 16 | 17 | (* always_ready, always_enabled *) 18 | method Bool locked; 19 | endinterface 20 | 21 | import "BVI" EHXPLLL = module mkPLL#(Clock clk_in, Reset rst_in) (EHXPLL); 22 | Bit#(1) zero = 0; 23 | Bit#(1) one = 1; 24 | 25 | parameter PLLRST_ENA = "DISABLED"; 26 | parameter INTFB_WAKE = "DISABLED"; 27 | parameter STDBY_ENABLE = "DISABLED"; 28 | parameter DPHASE_SOURCE = "DISABLED"; 29 | parameter OUTDIVIDER_MUXA = "DIVA"; 30 | parameter OUTDIVIDER_MUXB = "DIVB"; 31 | parameter OUTDIVIDER_MUXC = "DIVC"; 32 | parameter OUTDIVIDER_MUXD = "DIVD"; 33 | parameter CLKI_DIV = 1; 34 | parameter CLKOP_ENABLE = "ENABLED"; 35 | parameter CLKOP_DIV = 7; 36 | parameter CLKOP_CPHASE = 2; 37 | parameter CLKOP_FPHASE = 0; 38 | parameter FEEDBK_PATH = "CLKOP"; 39 | parameter CLKFB_DIV = 4; 40 | 41 | default_clock clki(CLKI, (* unused *) UNUSED) = clk_in; 42 | default_reset rsti = rst_in; 43 | 44 | method LOCK locked clocked_by(no_clock); 45 | 46 | output_clock clkop(CLKOP); 47 | port RST = zero; 48 | port STDBY = zero; 49 | port PHASESEL0 = zero; 50 | port PHASESEL1 = zero; 51 | port PHASEDIR = one; 52 | port PHASESTEP = one; 53 | port PHASELOADREG = one; 54 | port PLLWAKESYNC = zero; 55 | port ENCLKOP = zero; 56 | 57 | same_family(clki, clkop); 58 | endmodule 59 | 60 | endpackage 61 | -------------------------------------------------------------------------------- /hub75/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//build/bluespec:rules.bzl", "bluespec_library", "bluesim_test") 2 | 3 | bluespec_library( 4 | name = "hub75", 5 | srcs = [ 6 | "Hub75.bsv", 7 | ], 8 | deps = [ 9 | ":Gamma", 10 | ], 11 | visibility = ["//visibility:public"], 12 | ) 13 | 14 | bluespec_library( 15 | name = "Tb", 16 | srcs = [ 17 | "Tb.bsv", 18 | ], 19 | synthesize = { 20 | "Tb": ["mkTb"], 21 | }, 22 | deps = [ 23 | ":hub75", 24 | ], 25 | ) 26 | 27 | 28 | bluesim_test( 29 | name = "testbench", 30 | deps = [ 31 | ":Tb", 32 | ], 33 | top = "mkTb", 34 | ) 35 | 36 | py_binary( 37 | name = "gammagen", 38 | srcs = [ 39 | "gammagen.py", 40 | ], 41 | ) 42 | 43 | genrule( 44 | name = "gammagen_run", 45 | tools = [ 46 | ":gammagen", 47 | ], 48 | outs = [ 49 | "Gamma.bsv", 50 | ], 51 | cmd = "$(location :gammagen) $@", 52 | ) 53 | 54 | bluespec_library( 55 | name = "Gamma", 56 | srcs = [ 57 | ":Gamma.bsv", 58 | ], 59 | synthesize = { 60 | "Gamma": ["mkGammaExpand"], 61 | }, 62 | ) 63 | -------------------------------------------------------------------------------- /hub75/Hub75.bsv: -------------------------------------------------------------------------------- 1 | package Hub75; 2 | 3 | // This package implements an interface/driver for HUB75 LED panels. It's a 4 | // work in progress, but currently it's a self-contained sender which retrieves 5 | // data from a Coordinates/PixelData server (which can be plugged into eg. DRAM). 6 | // 7 | // In the future this might get split into a more modular design where a driver 8 | // can be fed data and control signals from the outside, with the full 'RAMDAC' 9 | // style design being a wrapper around that. 10 | 11 | import GetPut :: *; 12 | import ClientServer :: *; 13 | import FIFO :: *; 14 | import SpecialFIFOs :: *; 15 | import Vector :: *; 16 | import Probe :: *; 17 | import BRAM :: *; 18 | 19 | import Gamma :: *; 20 | 21 | typedef struct { 22 | Bit#(6) x; 23 | Bit#(TAdd#(4, TLog#(lines))) y; 24 | } Coordinates#(numeric type lines) deriving (Bits); 25 | 26 | typedef struct { 27 | Bit#(8) r; 28 | Bit#(8) g; 29 | Bit#(8) b; 30 | } PixelData deriving (Bits); 31 | 32 | typedef struct { 33 | Bit#(1) r; 34 | Bit#(1) g; 35 | Bit#(1) b; 36 | } LinePort deriving (Bits); 37 | 38 | typedef struct { 39 | Bit#(4) bank; 40 | Bit#(1) oe; 41 | Bit#(1) clk; 42 | Bit#(1) latch; 43 | Vector#(lines, LinePort) lines; 44 | } Port#(numeric type lines) deriving (Bits); 45 | 46 | function Port#(lines) defaultPort(); 47 | return Port { bank: 0 48 | , oe: 0 49 | , clk: 0 50 | , latch: 0 51 | , lines: replicate(defaultLinePort()) 52 | }; 53 | endfunction 54 | 55 | function LinePort defaultLinePort(); 56 | return LinePort { r: 0, g: 0, b: 0 }; 57 | endfunction 58 | 59 | interface Hub75#(numeric type lines); 60 | interface Client #(Coordinates#(lines), PixelData) pixel_data; 61 | 62 | (* always_enabled *) 63 | method Port#(lines) port; 64 | endinterface 65 | 66 | typedef struct { 67 | Bit#(12) r; 68 | Bit#(12) g; 69 | Bit#(12) b; 70 | } ColorExpanded deriving (Bits); 71 | 72 | instance DefaultValue#(ColorExpanded); 73 | defaultValue = ColorExpanded { r: 0 74 | , g: 0 75 | , b: 0 76 | }; 77 | endinstance 78 | 79 | typedef struct { 80 | Bit#(4) bank; 81 | Bit#(4) bitplane; 82 | } State deriving (Bits); 83 | 84 | typedef struct { 85 | State state; 86 | Bit#(6) x; 87 | Bool clk; 88 | Vector#(lines, ColorExpanded) colorData; 89 | } SendingState#(numeric type lines) deriving (Bits); 90 | 91 | typedef struct { 92 | State state; 93 | Bit#(12) counter; 94 | } OutputtingState deriving (Bits); 95 | 96 | typedef struct { 97 | State state; 98 | Bit#(6) x; 99 | Bit#(TLog#(lines)) line; 100 | } LoadingState#(numeric type lines) deriving (Bits); 101 | 102 | module mkHub75 (Hub75#(lines)); 103 | GammaExpand gammaR <- mkGammaExpand; 104 | GammaExpand gammaG <- mkGammaExpand; 105 | GammaExpand gammaB <- mkGammaExpand; 106 | 107 | Vector#(lines, Integer) lineNums = genWith(id); 108 | 109 | BRAM_Configure linebufCfg = defaultValue; 110 | // Don't need this functionality, disabling adds timing slack. 111 | linebufCfg.allowWriteResponseBypass = False; 112 | // Testing with sample design: 113 | // 3: 1784 LUT4s 114 | // 2: 1853 LUT4s 115 | // 1: 1739 LUT4s 116 | linebufCfg.outFIFODepth = 3; 117 | 118 | Vector#(lines, BRAM1Port#(Bit#(6), Bit#(36))) linebuf <- replicateM(mkBRAM1Server(linebufCfg)); 119 | 120 | Reg#(Maybe#(LoadingState#(lines))) loading <- mkReg(tagged Invalid); 121 | FIFO#(State) readyToLoad <- mkPipelineFIFO; 122 | 123 | Reg#(Maybe#(SendingState#(lines))) sending <- mkReg(tagged Invalid); 124 | FIFO#(State) readyToSend <- mkPipelineFIFO; 125 | 126 | Reg#(Bool) latching <- mkReg(False); 127 | FIFO#(State) readyToLatch <- mkPipelineFIFO; 128 | 129 | Reg#(Maybe#(OutputtingState)) outputting <- mkReg(tagged Invalid); 130 | FIFO#(State) readyToOutput <- mkPipelineFIFO; 131 | 132 | FIFO#(State) readyForNextLine <- mkPipelineFIFO; 133 | 134 | FIFO#(Coordinates#(lines)) loadRequest <- mkBypassFIFO; 135 | FIFO#(PixelData) loadResponse <- mkBypassFIFO; 136 | 137 | Vector#(lines, Bool) loadLineActive; 138 | let probeLoadActiveLine <- mkProbe; 139 | let probeLoadActiveX <- mkProbe; 140 | for (Integer i = 0; i < valueOf(lines); i = i + 1) begin 141 | loadLineActive[i] = False; 142 | case (loading) matches 143 | tagged Valid .ls: begin 144 | if (ls.line == fromInteger(i)) begin 145 | loadLineActive[i] = True; 146 | end 147 | end 148 | endcase 149 | end 150 | 151 | let probeBRAMWriteLine <- mkProbe; 152 | let probeBRAMWriteAddress <- mkProbe; 153 | let probeBRAMWriteData <- mkProbe; 154 | function Rules genLoadRules(Tuple2#(BRAM1Port#(Bit#(6), Bit#(36)), Integer) args); 155 | let port = tpl_1(args); 156 | let lineno = tpl_2(args); 157 | return (rules 158 | rule process_load_N (loadLineActive[lineno] &&& loading matches tagged Valid .state); 159 | let s = state; 160 | Bool done = False; 161 | 162 | probeLoadActiveLine <= fromInteger(lineno); 163 | probeLoadActiveX <= s.x; 164 | 165 | loadResponse.deq; 166 | let resp = loadResponse.first; 167 | let cd = ColorExpanded { r: gammaR.expand(resp.r) 168 | , g: gammaG.expand(resp.g) 169 | , b: gammaB.expand(resp.b) 170 | }; 171 | probeBRAMWriteLine <= fromInteger(lineno); 172 | probeBRAMWriteAddress <= s.x; 173 | probeBRAMWriteData <= pack(cd); 174 | linebuf[lineno].portA.request.put(BRAMRequest { write: True 175 | , responseOnWrite: False 176 | , address: s.x 177 | , datain: pack(cd) 178 | }); 179 | 180 | Bit#(TAdd#(4, TLog#(lines))) bankOffset = zeroExtend(s.line) << 4; 181 | if (s.x == 63) begin 182 | if (s.line == fromInteger((valueOf(lines) - 1))) begin 183 | done = True; 184 | end else begin 185 | s.x = 0; 186 | s.line = s.line + 1; 187 | bankOffset = zeroExtend(s.line) << 4; 188 | loadRequest.enq(Coordinates { x: s.x 189 | , y: zeroExtend(s.state.bank) + bankOffset 190 | }); 191 | end 192 | end else begin 193 | s.x = s.x + 1; 194 | loadRequest.enq(Coordinates { x: s.x 195 | , y: zeroExtend(s.state.bank) + bankOffset 196 | }); 197 | end 198 | 199 | if (done) begin 200 | readyToSend.enq(s.state); 201 | loading <= tagged Invalid; 202 | end else begin 203 | loading <= tagged Valid s; 204 | end 205 | endrule 206 | endrules); 207 | endfunction 208 | addRules(joinRules(map(genLoadRules, zip(linebuf, lineNums)))); 209 | 210 | rule start_load (loading matches tagged Invalid); 211 | readyToLoad.deq; 212 | let state = readyToLoad.first; 213 | loading <= tagged Valid LoadingState { state: state 214 | , x: 0 215 | , line: 0 216 | }; 217 | loadRequest.enq(Coordinates { x: 0 218 | , y: zeroExtend(state.bank) 219 | }); 220 | endrule 221 | 222 | let probeSendingX <- mkProbe; 223 | rule start_send (sending matches tagged Invalid); 224 | readyToSend.deq; 225 | let state = readyToSend.first; 226 | sending <= tagged Valid SendingState { state: state 227 | , x: 0 228 | , clk: False 229 | , colorData: replicate(defaultValue) 230 | }; 231 | for (Integer i = 0; i < valueOf(lines); i = i + 1) begin 232 | linebuf[i].portA.request.put(BRAMRequest{ write: False 233 | , responseOnWrite: False 234 | , address: 0 235 | , datain: 0 236 | }); 237 | end 238 | endrule 239 | (* mutually_exclusive = "process_load_N, process_send" *) 240 | rule process_send (sending matches tagged Valid .state); 241 | SendingState#(lines) s = state; 242 | Bool done = False; 243 | 244 | probeSendingX <= s.x; 245 | 246 | if (s.clk) begin 247 | s.clk = False; 248 | if (s.x == 63) begin 249 | readyToLatch.enq(s.state); 250 | done = True; 251 | end else begin 252 | let sxNext = s.x + 1; 253 | s.x = sxNext; 254 | for (Integer i = 0; i < valueOf(lines); i = i + 1) begin 255 | linebuf[i].portA.request.put(BRAMRequest{ write: False 256 | , responseOnWrite: False 257 | , address: sxNext 258 | , datain: 0 259 | }); 260 | end 261 | end 262 | end else begin 263 | for (Integer i = 0; i < valueOf(lines); i = i + 1) begin 264 | let resp <- linebuf[i].portA.response.get(); 265 | s.colorData[i] = unpack(resp); 266 | end 267 | s.clk = True; 268 | end 269 | if (done) begin 270 | sending <= tagged Invalid; 271 | end else begin 272 | sending <= tagged Valid s; 273 | end 274 | endrule 275 | 276 | rule start_latch (outputting matches tagged Invalid); 277 | readyToLatch.deq; 278 | readyToOutput.enq(readyToLatch.first); 279 | latching <= True; 280 | endrule 281 | (* preempts="end_latch, start_latch" *) 282 | rule end_latch (latching); 283 | latching <= False; 284 | endrule 285 | 286 | Reg#(Bit#(4)) prevBank <- mkReg(0); 287 | let probeOutputBitplane <- mkProbe; 288 | let probeOutputCounter <- mkProbe; 289 | rule start_output (outputting matches tagged Invalid); 290 | readyToOutput.deq; 291 | let state = readyToOutput.first; 292 | probeOutputBitplane <= state.bitplane; 293 | outputting <= tagged Valid OutputtingState { state: readyToOutput.first 294 | , counter: (1 << state.bitplane) 295 | }; 296 | readyForNextLine.enq(state); 297 | endrule 298 | rule process_output (outputting matches tagged Valid .state); 299 | let s = state; 300 | prevBank <= s.state.bank; 301 | Bool done = False; 302 | probeOutputCounter <= s.counter; 303 | if (s.counter > 0) begin 304 | s.counter = s.counter - 1; 305 | end else begin 306 | done = True; 307 | end 308 | if (done) begin 309 | outputting <= tagged Invalid; 310 | end else begin 311 | outputting <= tagged Valid s; 312 | end 313 | endrule 314 | 315 | rule process_next_line; 316 | readyForNextLine.deq; 317 | let state = readyForNextLine.first; 318 | 319 | if (state.bitplane == 11) begin 320 | state.bitplane = 0; 321 | if (state.bank == 15) begin 322 | state.bank = 0; 323 | end else begin 324 | state.bank = state.bank + 1; 325 | end 326 | end else begin 327 | state.bitplane = state.bitplane + 1; 328 | end 329 | readyToLoad.enq(state); 330 | endrule 331 | 332 | Reg#(Bool) kickstart <- mkReg(True); 333 | (* preempts = "process_kickstart, process_next_line" *) 334 | rule process_kickstart (kickstart); 335 | kickstart <= False; 336 | readyToLoad.enq(State { bank: 0 337 | , bitplane: 0 338 | }); 339 | endrule 340 | 341 | interface Client pixel_data; 342 | interface request = toGet(loadRequest); 343 | interface response = toPut(loadResponse); 344 | endinterface 345 | 346 | method Port#(lines) port; 347 | Port#(lines) pv = defaultPort(); 348 | 349 | let oe = case (outputting) matches 350 | tagged Invalid: False; 351 | tagged Valid .*: True; 352 | endcase; 353 | 354 | let bank = case (outputting) matches 355 | tagged Invalid: prevBank; 356 | tagged Valid .o: o.state.bank; 357 | endcase; 358 | 359 | case (sending) matches 360 | tagged Valid .s: begin 361 | for (Integer i = 0; i < valueOf(lines); i = i + 1) begin 362 | Bit#(1) r = s.colorData[i].r[s.state.bitplane]; 363 | Bit#(1) g = s.colorData[i].g[s.state.bitplane]; 364 | Bit#(1) b = s.colorData[i].b[s.state.bitplane]; 365 | pv.lines[i] = LinePort { r: r 366 | , g: g 367 | , b: b }; 368 | end 369 | pv.clk = pack(s.clk); 370 | end 371 | endcase 372 | 373 | pv.bank = pack(bank); 374 | pv.oe = pack(!oe); 375 | pv.latch = pack(latching); 376 | return pv; 377 | endmethod 378 | endmodule 379 | 380 | endpackage 381 | -------------------------------------------------------------------------------- /hub75/Tb.bsv: -------------------------------------------------------------------------------- 1 | package Tb; 2 | 3 | import GetPut :: *; 4 | import ClientServer :: *; 5 | import FIFO :: *; 6 | import Connectable :: *; 7 | import SpecialFIFOs :: *; 8 | import Probe :: *; 9 | 10 | import Hub75 :: *; 11 | 12 | module mkPatternGen (Server #(Coordinates#(2), PixelData)); 13 | FIFO#(Coordinates#(2)) fifoReq <- mkPipelineFIFO; 14 | 15 | let probeLoadRequestX <- mkProbe; 16 | let probeLoadRequestY <- mkProbe; 17 | 18 | interface Put request; 19 | method Action put(Coordinates#(2) c); 20 | fifoReq.enq(c); 21 | probeLoadRequestX <= c.x; 22 | probeLoadRequestY <= c.y; 23 | endmethod 24 | endinterface 25 | interface Get response; 26 | method ActionValue#(PixelData) get; 27 | fifoReq.deq; 28 | let r = fifoReq.first; 29 | let v = 0; 30 | if (r.y == 16) 31 | v = 255; 32 | return PixelData { r: v 33 | , g: 0 34 | , b: 0 35 | }; 36 | endmethod 37 | endinterface 38 | endmodule 39 | 40 | (* synthesize *) 41 | module mkTb (Empty); 42 | Reg#(int) i <- mkReg(0); 43 | 44 | Hub75#(2) hub75 <- mkHub75; 45 | let patternGen <- mkPatternGen; 46 | 47 | mkConnection(hub75.pixel_data, patternGen); 48 | 49 | rule testFetch; 50 | if (i > 100000) begin 51 | //bram.dump; 52 | $finish(0); 53 | end 54 | i <= i + 1; 55 | //$display("counter:", cpu.readPC); 56 | endrule 57 | 58 | let probeBank <- mkProbe; 59 | let probeOE <- mkProbe; 60 | let probeClk <- mkProbe; 61 | let probeLatch <- mkProbe; 62 | rule probeOut; 63 | probeBank <= hub75.port.bank; 64 | probeOE <= hub75.port.oe; 65 | probeClk <= hub75.port.clk; 66 | probeLatch <= hub75.port.latch; 67 | endrule 68 | endmodule 69 | 70 | endpackage 71 | -------------------------------------------------------------------------------- /hub75/gammagen.py: -------------------------------------------------------------------------------- 1 | import math 2 | import sys 3 | 4 | f = open(sys.argv[1], 'w') 5 | f.write("package Gamma;\n\n") 6 | 7 | f.write("interface GammaExpand;\n") 8 | f.write(" method Bit#(12) expand(Bit#(8) val);\n") 9 | f.write("endinterface\n\n") 10 | 11 | f.write("(* synthesize *)\n") 12 | f.write("module mkGammaExpand (GammaExpand);\n") 13 | f.write(" method Bit#(12) expand(Bit#(8) val);\n") 14 | f.write(" return case (val)\n") 15 | for i in range(256): 16 | g = int(4095 * math.pow(i/255, 2.2)) 17 | f.write(f" 8'h{i:02x}: 12'h{g:03x};\n") 18 | f.write(" endcase;\n") 19 | f.write(" endmethod\n") 20 | f.write("endmodule\n\n") 21 | 22 | f.write("endpackage") 23 | -------------------------------------------------------------------------------- /lanai/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//build/bluespec:rules.bzl", "bluespec_library", "bluesim_test") 2 | 3 | bluespec_library( 4 | name = "lanai", 5 | srcs = [ 6 | "CPU_Defs.bsv", 7 | "Lanai_IFC.bsv", 8 | "Lanai_Memory.bsv", 9 | ], 10 | deps = [ 11 | "//wishbone", 12 | "//wishbone/peripherals:spi", 13 | ], 14 | visibility = [ 15 | "//visibility:public", 16 | ], 17 | ) 18 | 19 | bluespec_library( 20 | name = "cpu", 21 | srcs = [ 22 | "CPU_ALU.bsv", 23 | "CPU_RegisterFile.bsv", 24 | "CPU_Compute.bsv", 25 | "CPU_Fetch.bsv", 26 | "CPU_Memory.bsv", 27 | "Lanai_CPU.bsv", 28 | ], 29 | deps = [ 30 | ":lanai", 31 | ], 32 | synthesize = { 33 | "Lanai_CPU": ["mkLanaiCPU"], 34 | "CPU_ALU": ["mkALU"], 35 | "CPU_RegisterFile": ["mkCPURegisterFile", "mkRFReg"], 36 | }, 37 | visibility = [ 38 | "//visibility:public", 39 | ], 40 | ) 41 | 42 | bluespec_library( 43 | name = "Tb", 44 | srcs = [ 45 | "Tb.bsv", 46 | ], 47 | synthesize = { 48 | "Tb": ["mkTb"], 49 | }, 50 | deps = [ 51 | ":cpu", 52 | ], 53 | data = [ 54 | ":bram.bin", 55 | ], 56 | ) 57 | 58 | genrule( 59 | name = "bram2", 60 | tools = [ 61 | "//lanai/qasm", 62 | ], 63 | cmd = "$(location //lanai/qasm) $@", 64 | outs = [ 65 | "bram2.bin" 66 | ], 67 | ) 68 | 69 | bluesim_test( 70 | name = "testbench", 71 | deps = [ 72 | ":Tb", 73 | ], 74 | top = "mkTb", 75 | ) 76 | -------------------------------------------------------------------------------- /lanai/CPU_ALU.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package CPU_ALU; 5 | 6 | import Vector :: *; 7 | 8 | import CPU_Defs :: *; 9 | 10 | typedef struct { 11 | Word a; 12 | Word b; 13 | AluOperationKind kind; 14 | Bool shiftArithmetic; 15 | Bool addCarry; 16 | Bool condition; 17 | } AluOperation deriving (Bits); 18 | 19 | typedef struct { 20 | Word result; 21 | StatusWord sw; 22 | } AluResult deriving (Bits); 23 | 24 | interface ALU_IFC; 25 | method ActionValue#(AluResult) run(AluOperation op); 26 | endinterface 27 | 28 | (* synthesize *) 29 | module mkALU (ALU_IFC); 30 | method ActionValue#(AluResult) run(AluOperation op); 31 | let res = AluResult { result: 0 32 | , sw: StatusWord { carry: False 33 | , overflow: False 34 | , zero: False 35 | , negative: False 36 | } 37 | }; 38 | 39 | Bit#(64) shiftIn = op.shiftArithmetic 40 | ? signExtend(op.a) 41 | : zeroExtend(op.a); 42 | // Make barrel shifter. 43 | function Bit#(33) makeShifts(Integer ix); 44 | if (ix < 32) begin 45 | return truncate(shiftIn << ix); 46 | end else begin 47 | Bit#(6) twoc = (~fromInteger(ix))+1; 48 | return truncate(shiftIn >> twoc); 49 | end 50 | endfunction 51 | Vector#(64, Bit#(33)) shifts = genWith(makeShifts); 52 | 53 | case (op.kind) matches 54 | Add: begin 55 | Bit#(33) out = zeroExtend(op.a) + zeroExtend(op.b) + zeroExtend(pack(op.addCarry)); 56 | res.sw.carry = out[32] == 1; 57 | res.result = out[31:0]; 58 | if ((op.a[31] == op.b[31]) && (op.a[31] != res.result[31])) begin 59 | res.sw.overflow = True; 60 | end 61 | end 62 | Sub: begin 63 | Bit#(33) out = zeroExtend(op.a) + zeroExtend(~op.b) + zeroExtend(pack(op.addCarry)); 64 | res.sw.carry = out[32] == 1; 65 | res.result = out[31:0]; 66 | if ((op.a[31] == op.b[31]) && (op.a[31] != res.result[31])) begin 67 | res.sw.overflow = True; 68 | end 69 | end 70 | And: res.result = op.a & op.b; 71 | Or: res.result = op.a | op.b; 72 | Xor: res.result = op.a ^ op.b; 73 | Shift: begin 74 | // TODO: optimize, this is our slowest path (42 -> 34MHz fclk). 75 | Bit#(6) amount = truncate(op.b); 76 | Bit#(33) shifted = shifts[amount]; 77 | res.result = shifted[31:0]; 78 | if ((amount > 0) && op.shiftArithmetic) 79 | res.sw.carry = shifted[32] == 1; 80 | end 81 | Select: begin 82 | res.result = op.condition ? op.a : op.b; 83 | end 84 | endcase 85 | 86 | res.sw.zero = (res.result == 0); 87 | res.sw.negative = (res.result[31] == 1); 88 | 89 | return res; 90 | endmethod 91 | endmodule 92 | 93 | 94 | endpackage 95 | -------------------------------------------------------------------------------- /lanai/CPU_Compute.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package CPU_Compute; 5 | 6 | import GetPut :: *; 7 | import DReg :: *; 8 | import FIFO :: *; 9 | import FIFOF :: *; 10 | import Probe :: *; 11 | import SpecialFIFOs :: *; 12 | 13 | import CPU_Defs :: *; 14 | import CPU_ALU :: *; 15 | 16 | interface CPU_Compute; 17 | interface Put #(FetchToCompute) fetch; 18 | interface Get #(ComputeToMemory) memory; 19 | interface RegisterWriteBypass memoryBypass; 20 | endinterface 21 | 22 | function Word resolveRegister(Register ix, Word pc, Maybe#(Tuple2#(Register, Word)) bypass, RegisterRead rs); 23 | Maybe#(Word) fromPC = tagged Invalid; 24 | Maybe#(Word) fromBypass = tagged Invalid; 25 | 26 | if (ix == PC) begin 27 | fromPC = tagged Valid pc; 28 | end 29 | if (bypass matches tagged Valid { .bix, .bval }) begin 30 | if (ix == bix) begin 31 | fromBypass = tagged Valid bval; 32 | end 33 | end 34 | 35 | return case (tuple2(fromBypass, fromPC)) matches 36 | { tagged Valid .bval, .* }: bval; 37 | { tagged Invalid, tagged Valid .pval }: pval; 38 | { tagged Invalid, tagged Invalid }: rs.read(ix); 39 | endcase; 40 | endfunction 41 | 42 | 43 | module mkCPUCompute #( RegisterRead rs1 44 | , RegisterRead rs2 45 | , StatusWordRead rsw 46 | , RegisterWriteCompute rwr 47 | , MispredictReport mispredict 48 | ) (CPU_Compute); 49 | 50 | FIFOF#(FetchToCompute) q <- mkPipelineFIFOF; 51 | RWire#(Tuple2#(Register, Word)) regFromMemory <- mkRWire; 52 | FIFO#(Misprediction) computedPC <- mkBypassFIFO; 53 | Reg#(Maybe#(Register)) memoryRegisterLoad <- mkReg(tagged Invalid); 54 | 55 | let busy <- mkPulseWire; 56 | let busyPut <- mkPulseWire; 57 | 58 | let busyProbe <- mkProbe; 59 | let busyPutProbe <- mkProbe; 60 | let fullQ <- mkProbe; 61 | let instrProbe <- mkProbe; 62 | let pcProbe <- mkProbe; 63 | ALU_IFC alu1 <- mkALU; 64 | 65 | rule updateBusy; 66 | busyProbe <= busy; 67 | endrule 68 | rule updateBusyPut; 69 | busyPutProbe <= busyPut; 70 | endrule 71 | rule updateFullQ; 72 | fullQ <= !q.notFull; 73 | endrule 74 | 75 | interface Get memory; 76 | method ActionValue#(ComputeToMemory) get(); 77 | busy.send(); 78 | 79 | let instr = q.first.instr; 80 | let instrPC = q.first.pc; 81 | let runAlu = q.first.runAlu; 82 | let destination = q.first.rd; 83 | instrProbe <= instr; 84 | pcProbe <= instrPC; 85 | StatusWord sw = rsw.read; 86 | 87 | let res = ComputeToMemory { ea: 0 88 | , op: tagged Noop 89 | , pc: instrPC 90 | , width: Word }; 91 | 92 | Register rs1ix = q.first.rs1; 93 | Register rs2ix = q.first.rs2; 94 | // ... unless we're in SPLS, rs2 is rs2, otherwise it's... rd? Annoying mux. 95 | case (instr) matches 96 | tagged SLS .sls: begin 97 | rs2ix = sls.destination; 98 | end 99 | tagged SPLS .spls: begin 100 | rs2ix = spls.destination; 101 | end 102 | endcase 103 | 104 | // Lanai11 delays register reads after register loads. 105 | let delayRegisterLoad = False; 106 | if (memoryRegisterLoad matches tagged Valid .rd) begin 107 | if (rs1ix == destination || rs2ix == destination) begin 108 | delayRegisterLoad = True; 109 | end 110 | end 111 | 112 | if (delayRegisterLoad) begin 113 | memoryRegisterLoad <= tagged Invalid; 114 | $display("%x: COMP: delay", instrPC); 115 | return res; 116 | end else begin 117 | q.deq; 118 | // Optimization: always read source1/source2 regs, as they use the same opcode positions. 119 | //$display("%x: BYPA: ", instrPC, fshow(regFromMemory.wget())); 120 | let rs1v = resolveRegister(rs1ix, instrPC, regFromMemory.wget(), rs1); 121 | let rs2v = resolveRegister(rs2ix, instrPC, regFromMemory.wget(), rs2); 122 | 123 | // Optimization: always build arithmetic op. 124 | let aluOp = AluOperation { a: rs1v 125 | , b: 0 126 | , shiftArithmetic: False 127 | , addCarry: sw.carry 128 | , kind: q.first.aluOpKind 129 | , condition: False 130 | }; 131 | 132 | Bool flags = False; 133 | Maybe#(Tuple2#(Register, Word)) mrd = tagged Invalid; 134 | Maybe#(StatusWord) msw = tagged Invalid; 135 | 136 | 137 | //$display("%x: COMP: ", instrPC, fshow(instr)); 138 | case (instr) matches 139 | tagged RI .ri: begin 140 | let shift = ri.high ? 16 : 0; 141 | let czshift = zeroExtend(ri.constant) << shift; 142 | let coshift = ri.high ? { ri.constant, 16'hFFFF } : { 16'hFFFF, ri.constant }; 143 | aluOp.b = czshift; 144 | aluOp.shiftArithmetic = ri.high; 145 | 146 | case (ri.operation) matches 147 | Add: begin 148 | aluOp.addCarry = False; 149 | end 150 | Sub: begin 151 | aluOp.addCarry = True; 152 | end 153 | And: begin 154 | aluOp.b = coshift; 155 | end 156 | Shift: begin 157 | aluOp.b = signExtend(ri.constant); 158 | end 159 | endcase 160 | 161 | flags = ri.flags; 162 | end 163 | tagged RR .rr: begin 164 | aluOp.b = rs2v; 165 | case (rr.operation) matches 166 | Add: begin 167 | aluOp.addCarry = False; 168 | end 169 | Sub: begin 170 | aluOp.addCarry = True; 171 | end 172 | AShift: begin 173 | aluOp.shiftArithmetic = True; 174 | end 175 | Select: begin 176 | aluOp.condition = evaluateCondition(rr.condition, sw); 177 | $display("eval cond code: ", rr.condition, ", sw: ", fshow(sw), ", res: ", aluOp.condition); 178 | end 179 | endcase 180 | 181 | flags = rr.flags; 182 | end 183 | tagged RM .rm: begin 184 | Word added = (rs1v + signExtend(rm.constant))[31:0]; 185 | Word ea = rm.p ? added : rs1v; 186 | if (rm.q) begin 187 | mrd = tagged Valid tuple2(q.first.rs1, added); 188 | end 189 | res.ea = ea; 190 | res.op = (rm.store ? tagged Store rs2v : tagged Load destination); 191 | end 192 | tagged BR .br: begin 193 | if (evaluateCondition(br.condition, sw)) begin 194 | $display("%x: BRAN: take", instrPC); 195 | Word newPC = br.r ? unpack(signExtend(br.constant << 2) + instrPC) 196 | : unpack(zeroExtend(br.constant << 2)); 197 | mrd = tagged Valid tuple2(PC, newPC); 198 | end else begin 199 | $display("%x: BRAN: skip ", instrPC, fshow(br.condition), fshow(sw)); 200 | end 201 | end 202 | tagged SLS .sls: begin 203 | res.ea = zeroExtend(sls.address); 204 | res.op = sls.s ? tagged Store rs2v : tagged Load destination; 205 | end 206 | tagged SLI .sli: begin 207 | Word imm = zeroExtend(sli.address); 208 | mrd = tagged Valid tuple2(destination, imm); 209 | $display("%x: COMP ", instrPC, fshow(sli)); 210 | end 211 | tagged SPLS .spls: begin 212 | Word added = (rs1v + signExtend(spls.constant))[31:0]; 213 | Word ea = spls.p ? added : rs1v; 214 | if (spls.q) begin 215 | mrd = tagged Valid tuple2(q.first.rs1, added); 216 | end 217 | res.ea = ea; 218 | let rrv = resolveRegister(spls.destination, instrPC, regFromMemory.wget(), rs2); 219 | case (spls) matches 220 | InstSPLS { s: True, y: True }: begin 221 | res.op = tagged Store rs2v; 222 | res.width = Byte; 223 | end 224 | InstSPLS { s: False, y: True }: begin 225 | res.op = tagged Store rs2v; 226 | res.width = Word; 227 | end 228 | InstSPLS { s: True, y: False }: begin 229 | res.op = tagged Load rs2ix; 230 | res.width = Byte; 231 | end 232 | InstSPLS { s: False, y: False }: begin 233 | res.op = tagged Load rs2ix; 234 | res.width = Word; 235 | end 236 | endcase 237 | end 238 | endcase 239 | 240 | if (runAlu) begin 241 | let aluRes <- alu1.run(aluOp); 242 | mrd = tagged Valid tuple2(destination, aluRes.result); 243 | if (flags) begin 244 | $display("%x: FLAG ", instrPC, fshow(aluRes.sw)); 245 | msw = tagged Valid aluRes.sw; 246 | end 247 | end 248 | case (mrd) matches 249 | tagged Valid { PC, .val }: begin 250 | mispredict.put(Misprediction { pc : val 251 | , opc: instrPC + 8 252 | }); 253 | end 254 | endcase 255 | if (res.op matches tagged Load .rd) begin 256 | memoryRegisterLoad <= tagged Valid rd; 257 | end else begin 258 | memoryRegisterLoad <= tagged Invalid; 259 | end 260 | rwr.write(msw, mrd); 261 | return res; 262 | end 263 | endmethod 264 | endinterface 265 | 266 | interface Put fetch; 267 | method Action put(FetchToCompute v); 268 | busyPut.send(); 269 | q.enq(v); 270 | endmethod 271 | endinterface 272 | 273 | interface RegisterWriteBypass memoryBypass; 274 | method Action strobe(Register ix, Word value); 275 | regFromMemory.wset(tuple2(ix, value)); 276 | endmethod 277 | endinterface 278 | endmodule 279 | 280 | endpackage 281 | -------------------------------------------------------------------------------- /lanai/CPU_Fetch.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package CPU_Fetch; 5 | 6 | import ClientServer :: *; 7 | import ConfigReg :: *; 8 | import GetPut :: *; 9 | import FIFO :: *; 10 | import FIFOF :: *; 11 | import Probe :: *; 12 | import SpecialFIFOs :: *; 13 | 14 | import Lanai_IFC :: *; 15 | import CPU_Defs :: *; 16 | 17 | interface CPU_Fetch; 18 | interface Get #(FetchToCompute) compute; 19 | interface Client #(Word, Word) imem; 20 | 21 | interface MispredictReport mispredictCompute; 22 | interface MispredictReport mispredictMemory; 23 | endinterface 24 | 25 | module mkCPUFetch #( RegisterRead pcRead 26 | ) (CPU_Fetch); 27 | 28 | Reg#(Word) cPredictCount <- mkConfigReg(0); 29 | Reg#(Word) cMispredictOkCount <- mkConfigReg(0); 30 | Reg#(Word) cMispredictLagCount <- mkConfigReg(0); 31 | Reg#(Word) cMispredictErrorCount <- mkConfigReg(0); 32 | 33 | let pcProbe <- mkProbe; 34 | let wantProbe <- mkProbe; 35 | let fetchProbe <- mkProbe; 36 | let putProbe <- mkProbe; 37 | let getProbe <- mkProbe; 38 | let mispredictErrorProbe <- mkProbe; 39 | 40 | FIFOF#(FetchToCompute) out <- mkBypassFIFOF; 41 | Reg#(Word) cycle <- mkReg(0); 42 | Reg#(Word) wantPC <- mkReg(0); 43 | Reg#(Word) fetchPC <- mkWire; 44 | 45 | FIFOF#(Misprediction) mispredictComputeF <- mkBypassFIFOF; 46 | FIFOF#(Misprediction) mispredictMemoryF <- mkBypassFIFOF; 47 | 48 | FIFO#(Word) pcRequested <- mkPipelineFIFO; 49 | FIFOF#(Tuple2#(Word, Word)) fetched <- mkBypassFIFOF; 50 | 51 | rule upCycle; 52 | cycle <= cycle + 1; 53 | endrule 54 | 55 | rule kickstart (cycle == 0); 56 | fetchPC <= 0; 57 | endrule 58 | 59 | rule wantProbeUpdate; 60 | wantProbe <= wantPC; 61 | endrule 62 | 63 | rule fetchProbeUpdate; 64 | fetchProbe <= fetchPC; 65 | endrule 66 | 67 | rule mispredictProbeUpdate; 68 | mispredictErrorProbe <= cMispredictErrorCount; 69 | endrule 70 | 71 | interface Get compute; 72 | method ActionValue#(FetchToCompute) get() if (cycle != 0); 73 | fetched.deq; 74 | match { .pc, .data } = fetched.first; 75 | //$display("%d: trySchedule %x", cycle, pc); 76 | 77 | Maybe#(Misprediction) override = tagged Invalid; 78 | if (mispredictComputeF.notEmpty) begin 79 | mispredictComputeF.deq; 80 | override = tagged Valid mispredictComputeF.first; 81 | end 82 | if (mispredictMemoryF.notEmpty) begin 83 | mispredictMemoryF.deq; 84 | override = tagged Valid mispredictMemoryF.first; 85 | end 86 | 87 | let sched = True; 88 | let nextPC = pc + 4; 89 | if (override matches tagged Valid .mispredict) begin 90 | $display("%d: mispredict:", cycle, fshow(mispredict)); 91 | 92 | if (mispredict.pc != wantPC) begin 93 | if (mispredict.opc == pc+4) begin 94 | cMispredictOkCount <= cMispredictOkCount + 1; 95 | $display("%d: pc miss, %x -> %x", cycle, pc, mispredict.pc); 96 | end else if (mispredict.opc == pc) begin 97 | cMispredictLagCount <= cMispredictLagCount + 1; 98 | $display("%d: pc Miss, %x -> %x", cycle, pc, mispredict.pc); 99 | sched = False; 100 | end else if (mispredict.opc == pc-4) begin 101 | cMispredictLagCount <= cMispredictLagCount + 1; 102 | $display("%d: pc Miss, %x -> %x", cycle, pc, mispredict.pc); 103 | sched = False; 104 | end else begin 105 | cMispredictErrorCount <= cMispredictErrorCount + 1; 106 | $display("%d: pc MISS, %x -> %x", cycle, pc, mispredict.pc); 107 | sched = False; 108 | end 109 | nextPC = mispredict.pc; 110 | end 111 | end 112 | wantPC <= nextPC; 113 | fetchPC <= nextPC; 114 | 115 | if (nextPC == pc + 4) begin 116 | cPredictCount <= cPredictCount + 1; 117 | //$display("%d: pc okay, %x", cycle, pc); 118 | end 119 | 120 | Instruction instr = tagged RI InstRI { operation: unpack(0) 121 | , flags: unpack(0) 122 | , high: unpack(0) 123 | , destination: unpack(0) 124 | , source: unpack(0) 125 | , constant: unpack(0) 126 | }; 127 | if (sched) begin 128 | Instruction instr2 = unpack(data); 129 | if (instr2 matches tagged Unknown .x) begin 130 | //$display("Invalid instruction: ", x); 131 | end else begin 132 | instr = instr2; 133 | end 134 | Bool runAlu = case(instr) matches 135 | tagged RI .ri: True; 136 | tagged RR .rr: True; 137 | default: False; 138 | endcase; 139 | Register rs2 = case(instr) matches 140 | tagged RM .rm: unpack(data[27:23]); 141 | default: unpack(data[15:11]); 142 | endcase; 143 | 144 | pcProbe <= pc; 145 | return FetchToCompute { instr: instr 146 | , pc: pc 147 | , rs1: unpack(data[22:18]) 148 | , rs2: rs2 149 | , rd: unpack(data[27:23]) 150 | , runAlu: runAlu 151 | , aluOpKind: insAluOpKind(instr) 152 | }; 153 | end else begin 154 | return FetchToCompute { instr: instr, pc: 0, rs1: R0, rs2: R0, rd: R0, runAlu: False, aluOpKind: tagged Add }; 155 | end 156 | endmethod 157 | endinterface 158 | 159 | interface Client imem; 160 | interface Get request; 161 | method ActionValue#(Word) get if (fetchPC < sysmemSplit); 162 | let pc = fetchPC; 163 | //$display("%d get pc: %x", cycle, pc); 164 | pcRequested.enq(pc); 165 | getProbe <= pc; 166 | return pc; 167 | endmethod 168 | endinterface 169 | interface Put response; 170 | method Action put(Word data) if (pcRequested.first < sysmemSplit); 171 | pcRequested.deq; 172 | let pc = pcRequested.first; 173 | putProbe <= pc; 174 | //$display("%d put pc: %x, data: %x", cycle, pc, data); 175 | fetched.enq(tuple2(pc, data)); 176 | endmethod 177 | endinterface 178 | endinterface 179 | 180 | interface MispredictReport mispredictCompute = toPut(mispredictComputeF); 181 | interface MispredictReport mispredictMemory = toPut(mispredictMemoryF); 182 | endmodule 183 | 184 | endpackage 185 | -------------------------------------------------------------------------------- /lanai/CPU_Memory.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package CPU_Memory; 5 | 6 | import ClientServer :: *; 7 | import GetPut :: *; 8 | import FIFO :: *; 9 | import FIFOF :: *; 10 | import Probe :: *; 11 | import SpecialFIFOs :: *; 12 | import Wishbone :: *; 13 | 14 | import Lanai_IFC :: *; 15 | import CPU_Defs :: *; 16 | 17 | interface CPU_Memory; 18 | interface Put #(ComputeToMemory) compute; 19 | interface Client #(DMemReq, Word) dmem; 20 | interface Wishbone::Master #(32, 32, 4) sysmem; 21 | endinterface 22 | 23 | typedef struct { 24 | Maybe#(Register) rd; 25 | Word pc; 26 | Word ea; 27 | } WaitReadResponse deriving (Bits); 28 | 29 | module mkCPUMemory #( RegisterWriteMemory rwr 30 | , RegisterWriteBypass bypass 31 | , MispredictReport mispredict 32 | ) (CPU_Memory); 33 | 34 | FIFOF #(ComputeToMemory) q <- mkPipelineFIFOF; 35 | FIFOF #(WaitReadResponse) waitRead <- mkPipelineFIFOF; 36 | FIFO #(Misprediction) computedPC <- mkBypassFIFO; 37 | Reg#(Bool) pendingPCLoad <- mkReg(False); 38 | Reg#(Bool) startPCLoad <- mkDWire(False); 39 | Reg#(Bool) stopPCLoad <- mkDWire(False); 40 | let pcLoadStall = pendingPCLoad; 41 | FIFO#(Wishbone::SlaveResponse#(32)) delaySysmemResponse <- mkPipelineFIFO; 42 | 43 | Wishbone::MasterConnector#(32, 32, 4) sysmemMaster <- mkMasterConnector; 44 | 45 | PulseWire busyReq <- mkPulseWire; 46 | PulseWire busyResp <- mkPulseWire; 47 | PulseWire busyPut <- mkPulseWire; 48 | 49 | let busyReqProbe <- mkProbe; 50 | let busyRespProbe <- mkProbe; 51 | let busyPutProbe <- mkProbe; 52 | let fullQ <- mkProbe; 53 | let fullWaitRead <- mkProbe; 54 | 55 | let eaProbe <- mkProbe; 56 | let responseRegProbe <- mkProbe; 57 | 58 | rule updateBusyProbe; 59 | busyReqProbe <= busyReq; 60 | busyRespProbe <= busyResp; 61 | endrule 62 | rule updateBusyPutProbe; 63 | busyPutProbe <= busyPut; 64 | endrule 65 | rule updateFullQ; 66 | fullQ <= !q.notFull; 67 | endrule 68 | rule updateFullWaitRead; 69 | fullWaitRead <= !waitRead.notFull; 70 | endrule 71 | 72 | rule setPend; 73 | if (startPCLoad) begin 74 | pendingPCLoad <= True; 75 | end else begin 76 | if (stopPCLoad) begin 77 | pendingPCLoad <= False; 78 | end 79 | end 80 | endrule 81 | 82 | rule sysmemRequest(!pcLoadStall && q.first.ea >= sysmemSplit); 83 | q.deq; 84 | busyReq.send(); 85 | eaProbe <= q.first.ea; 86 | let pc = q.first.pc; 87 | Maybe#(Bit#(32)) data = tagged Invalid; 88 | Bool spurious = False; 89 | case (q.first.op) matches 90 | tagged Noop: begin 91 | waitRead.enq(WaitReadResponse { pc: pc, ea: q.first.ea, rd: tagged Invalid }); 92 | spurious = True; 93 | end 94 | tagged Load .rd: begin 95 | waitRead.enq(WaitReadResponse { pc: pc, ea: q.first.ea, rd: tagged Valid rd }); 96 | if (rd == PC) begin 97 | startPCLoad <= True; 98 | end 99 | end 100 | tagged Store .d: begin 101 | waitRead.enq(WaitReadResponse { pc: pc, ea: q.first.ea, rd: tagged Invalid }); 102 | data = tagged Valid d; 103 | end 104 | endcase 105 | sysmemMaster.server.request.put(SlaveRequest { address: q.first.ea 106 | , writeData: data 107 | , select: 4'b1111 108 | }); 109 | endrule 110 | 111 | rule sysmemResponseDelay; 112 | let data <- sysmemMaster.server.response.get(); 113 | delaySysmemResponse.enq(data); 114 | endrule 115 | 116 | rule sysmemResponse(waitRead.first.ea >= sysmemSplit); 117 | waitRead.deq; 118 | let resp = delaySysmemResponse.first; 119 | delaySysmemResponse.deq(); 120 | 121 | let data = fromMaybe(0, resp.readData); 122 | 123 | if (waitRead.first.rd matches tagged Valid .rd) begin 124 | busyResp.send(); 125 | responseRegProbe <= rd; 126 | if (rd == PC) begin 127 | stopPCLoad <= True; 128 | mispredict.put(Misprediction { pc: data 129 | , opc: waitRead.first.pc + 8 130 | }); 131 | end else begin 132 | rwr.write(rd, data); 133 | bypass.strobe(rd, data); 134 | end 135 | end 136 | endrule 137 | 138 | interface Client dmem; 139 | interface Get request; 140 | method ActionValue#(DMemReq) get if (!pcLoadStall && q.first.ea < sysmemSplit); 141 | busyReq.send(); 142 | 143 | q.deq; 144 | eaProbe <= q.first.ea; 145 | let pc = q.first.pc; 146 | 147 | 148 | Maybe#(Word) data = tagged Invalid; 149 | Bool spurious = False; 150 | case (q.first.op) matches 151 | tagged Noop: begin 152 | waitRead.enq(WaitReadResponse { pc: pc, ea: q.first.ea, rd: tagged Invalid }); 153 | spurious = True; 154 | end 155 | tagged Load .rd: begin 156 | waitRead.enq(WaitReadResponse { pc: pc, ea: q.first.ea, rd: tagged Valid rd }); 157 | if (rd == PC) begin 158 | startPCLoad <= True; 159 | end 160 | end 161 | tagged Store .d: begin 162 | waitRead.enq(WaitReadResponse { pc: pc, ea: q.first.ea, rd: tagged Invalid }); 163 | data = tagged Valid d; 164 | end 165 | endcase 166 | 167 | return DMemReq { addr: q.first.ea 168 | , width: q.first.width 169 | , data: data 170 | , spurious: spurious 171 | , pc: pc 172 | }; 173 | endmethod 174 | endinterface 175 | 176 | interface Put response; 177 | method Action put(Word resp); 178 | waitRead.deq; 179 | if (waitRead.first.rd matches tagged Valid .rd) begin 180 | busyResp.send(); 181 | responseRegProbe <= rd; 182 | if (rd == PC) begin 183 | stopPCLoad <= True; 184 | mispredict.put(Misprediction { pc: resp 185 | , opc: waitRead.first.pc + 8 186 | }); 187 | end else begin 188 | rwr.write(rd, resp); 189 | bypass.strobe(rd, resp); 190 | end 191 | end 192 | endmethod 193 | endinterface 194 | endinterface 195 | 196 | 197 | interface Put compute; 198 | method Action put(ComputeToMemory v); 199 | busyPut.send(); 200 | q.enq(v); 201 | endmethod 202 | endinterface 203 | 204 | interface sysmem = sysmemMaster.master; 205 | endmodule 206 | 207 | endpackage 208 | -------------------------------------------------------------------------------- /lanai/CPU_RegisterFile.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package CPU_RegisterFile; 5 | 6 | import ConfigReg :: *; 7 | import Vector :: *; 8 | 9 | import CPU_Defs :: *; 10 | 11 | interface CPU_RegisterFile; 12 | interface RegisterRead fetchRead; 13 | 14 | interface RegisterRead computeSource1; 15 | interface RegisterRead computeSource2; 16 | interface StatusWordRead computeStatusSource; 17 | interface RegisterWriteCompute computeWrite; 18 | interface RegisterWriteMemory memoryWrite; 19 | endinterface 20 | 21 | module mkConstantReg #(Word val) 22 | (Reg#(Word)); 23 | method Word _read; 24 | return val; 25 | endmethod 26 | method Action _write(Word w); 27 | endmethod 28 | endmodule 29 | 30 | (* synthesize *) 31 | module mkRFReg(Reg#(Word)); 32 | let res <- mkConfigReg(0); 33 | return res; 34 | endmodule 35 | 36 | typedef Wire#(Tuple2#(Register, Word)) WriteReq; 37 | 38 | (* synthesize *) 39 | module mkCPURegisterFile(CPU_RegisterFile); 40 | function m#(Reg#(Word)) makeRegs(Integer ix) provisos (IsModule#(m, c)); 41 | return case (ix) matches 42 | 0: mkConstantReg(32'h00000000); 43 | 1: mkConstantReg(32'hFFFFFFFF); 44 | 2: mkConstantReg(32'hDEADBEEF); 45 | 4: mkConfigReg(32'h20002000); 46 | default: mkRFReg; 47 | endcase; 48 | endfunction 49 | Vector#(32, Reg#(Word)) regs <- genWithM(makeRegs); 50 | Reg#(Word) status <- mkReg(0); 51 | 52 | WriteReq writeReqCompute <- mkWire; 53 | WriteReq writeReqMemory <- mkWire; 54 | 55 | Vector #(2, WriteReq) writeReqs; 56 | writeReqs[0] = writeReqCompute; 57 | writeReqs[1] = writeReqMemory; 58 | 59 | 60 | Vector #(29, Register) writableRegisters; 61 | for (Integer i = 0; i < 29; i = i + 1) begin 62 | Bit#(32) no = fromInteger(i + 3); 63 | writableRegisters[i] = unpack(no[4:0]); 64 | end 65 | 66 | function Rules genRegRules(Register regno); 67 | function Rules genRegRule(WriteReq wr); 68 | return (rules 69 | rule foo if (tpl_1(wr) == regno); 70 | regs[pack(regno)] <= tpl_2(wr); 71 | endrule 72 | endrules); 73 | endfunction 74 | return foldl1(rJoinPreempts, map(genRegRule, writeReqs)); 75 | endfunction 76 | addRules(joinRules(map(genRegRules, writableRegisters))); 77 | 78 | function RegisterRead makeRead(); 79 | return (interface RegisterRead; 80 | method Word read(Register ix); 81 | return regs[pack(ix)]; 82 | endmethod 83 | endinterface); 84 | endfunction 85 | 86 | interface RegisterRead fetchRead = makeRead; 87 | interface RegisterRead computeSource1 = makeRead; 88 | interface RegisterRead computeSource2 = makeRead; 89 | interface StatusWordRead computeStatusSource; 90 | method StatusWord read = unpack(status); 91 | endinterface 92 | 93 | interface RegisterWriteCompute computeWrite; 94 | method Action write( Maybe#(StatusWord) sw 95 | , Maybe#(Tuple2#(Register, Word)) rd 96 | ); 97 | 98 | case (sw) matches 99 | tagged Valid .swd: begin 100 | status <= pack(swd); 101 | end 102 | endcase 103 | case (rd) matches 104 | tagged Valid .rdd: begin 105 | writeReqCompute <= rdd; 106 | end 107 | endcase 108 | endmethod 109 | endinterface 110 | 111 | interface RegisterWriteMemory memoryWrite; 112 | method Action write(Register rd, Word value); 113 | writeReqMemory <= tuple2(rd, value); 114 | endmethod 115 | endinterface 116 | endmodule 117 | 118 | 119 | endpackage 120 | -------------------------------------------------------------------------------- /lanai/Lanai_CPU.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package Lanai_CPU; 5 | 6 | import GetPut :: *; 7 | import Connectable :: *; 8 | import Wishbone :: *; 9 | import WishboneCrossbar :: *; 10 | 11 | import Lanai_IFC :: *; 12 | import CPU_Defs :: *; 13 | import CPU_RegisterFile :: *; 14 | import CPU_Fetch :: *; 15 | import CPU_Compute :: *; 16 | import CPU_Memory :: *; 17 | 18 | function Maybe#(WishboneCrossbar::DecodedAddr#(1, 32)) decoder(Bit#(32) address); 19 | return tagged Valid DecodedAddr { downstream: 0, address: address }; 20 | endfunction 21 | 22 | (* synthesize *) 23 | module mkLanaiCPU (Lanai_IFC); 24 | CPU_RegisterFile rf <- mkCPURegisterFile; 25 | CPU_Fetch fetch <- mkCPUFetch( rf.fetchRead 26 | ); 27 | CPU_Compute compute <- mkCPUCompute( rf.computeSource1 28 | , rf.computeSource2 29 | , rf.computeStatusSource 30 | , rf.computeWrite 31 | , fetch.mispredictCompute 32 | ); 33 | CPU_Memory memory <- mkCPUMemory( rf.memoryWrite 34 | , compute.memoryBypass 35 | , fetch.mispredictMemory 36 | ); 37 | 38 | mkConnection(fetch.compute, compute.fetch); 39 | mkConnection(compute.memory, memory.compute); 40 | 41 | interface imem_client = fetch.imem; 42 | interface dmem_client = memory.dmem; 43 | interface sysmem_client = memory.sysmem; 44 | endmodule 45 | 46 | endpackage 47 | -------------------------------------------------------------------------------- /lanai/Lanai_IFC.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package Lanai_IFC; 5 | 6 | import GetPut :: *; 7 | import ClientServer :: *; 8 | import Wishbone :: *; 9 | 10 | import CPU_Defs :: *; 11 | 12 | export Word, Lanai_IFC (..), DMemReq (..), DMemReqWidth (..); 13 | 14 | typedef struct { 15 | Word addr; 16 | Maybe#(Word) data; 17 | DMemReqWidth width; 18 | Bool spurious; 19 | Word pc; 20 | } DMemReq deriving (Bits); 21 | 22 | interface Lanai_IFC; 23 | interface Client #(DMemReq, Word) dmem_client; 24 | interface Client #(Word, Word) imem_client; 25 | interface Wishbone::Master #(32, 32, 4) sysmem_client; 26 | method Word readPC; 27 | endinterface 28 | 29 | endpackage 30 | -------------------------------------------------------------------------------- /lanai/Lanai_Memory.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package Lanai_Memory; 5 | 6 | import BRAM :: *; 7 | import FIFO :: *; 8 | import SpecialFIFOs :: *; 9 | 10 | import CPU_Defs :: *; 11 | import Lanai_IFC :: *; 12 | 13 | interface Lanai_Memory; 14 | interface Server#(Word, Word) imem; 15 | interface Server#(DMemReq, Word) dmem; 16 | endinterface 17 | 18 | // n is the amount of kilowords of RAM available. 19 | interface Lanai_BlockRAM#(numeric type n); 20 | interface Lanai_Memory memory; 21 | endinterface 22 | 23 | module mkBlockMemory#(Maybe#(String) filename) (Lanai_BlockRAM#(k)) provisos (Log#(k, n)); 24 | BRAM_Configure cfg = defaultValue; 25 | cfg.latency = 1; 26 | if (filename matches tagged Valid .fn) begin 27 | cfg.loadFormat = tagged Hex fn; 28 | end 29 | cfg.outFIFODepth = 3; 30 | cfg.allowWriteResponseBypass = False; 31 | BRAM2PortBE#(Bit#(n), Bit#(32), 4) bram <- mkBRAM2ServerBE(cfg); 32 | 33 | FIFO#(BRAMRequestBE#(Bit#(n), Bit#(32), 4)) delayFIFO <- mkPipelineFIFO; 34 | 35 | FIFO#(DMemReq) waitQ <- mkPipelineFIFO; 36 | 37 | let nwords = valueOf(n); 38 | 39 | rule delayed_dmem; 40 | delayFIFO.deq; 41 | let breq = delayFIFO.first; 42 | bram.portB.request.put(breq); 43 | endrule 44 | 45 | interface Lanai_Memory memory; 46 | interface Server imem; 47 | interface Put request; 48 | method Action put(Word addr); 49 | bram.portA.request.put(BRAMRequestBE { writeen: 4'b000 50 | , responseOnWrite: True 51 | , address: addr[nwords+1:2] 52 | , datain: 0 53 | }); 54 | endmethod 55 | endinterface 56 | interface Get response = bram.portA.response; 57 | endinterface 58 | 59 | interface Server dmem; 60 | interface Put request; 61 | method Action put(DMemReq req); 62 | waitQ.enq(req); 63 | Bit#(n) addr = req.addr[nwords+1:2]; 64 | let breq = BRAMRequestBE { writeen: 4'b0000 65 | , responseOnWrite: True 66 | , address: addr 67 | , datain: 0 68 | }; 69 | if (!req.spurious) begin 70 | case (req.data) matches 71 | tagged Valid .val: begin 72 | case (req.width) matches 73 | tagged Word: begin 74 | $display("%x: DMEM WRITE REQ, word [%x] <- %x", req.pc, req.addr, val); 75 | breq.writeen = 4'b1111; 76 | breq.datain = val; 77 | end 78 | tagged HalfWord: begin 79 | Bit#(32) valH = zeroExtend(val[15:0]); 80 | $display("%x: DMEM WRITE REQ, hword [%x] <- %x", req.pc, req.addr, valH); 81 | case (req.addr[1]) matches 82 | 1'b1: begin 83 | breq.writeen = 4'b0011; 84 | breq.datain = valH; 85 | end 86 | 1'b0: begin 87 | breq.writeen = 4'b1100; 88 | breq.datain = valH << 16; 89 | end 90 | endcase 91 | if (req.addr[1] == 1) begin 92 | breq.writeen = 4'b1100; 93 | breq.datain = val << 16; 94 | end else begin 95 | breq.writeen = 4'b0011; 96 | breq.datain = zeroExtend(val[15:0]); 97 | end 98 | end 99 | tagged Byte: begin 100 | Bit#(32) valB = zeroExtend(val[7:0]); 101 | $display("%x: DMEM WRITE REQ, byte [%x] <- %x", req.pc, req.addr, valB); 102 | case (req.addr[1:0]) matches 103 | 2'b11: begin 104 | breq.writeen = 4'b0001; 105 | breq.datain = valB; 106 | end 107 | 2'b10: begin 108 | breq.writeen = 4'b0010; 109 | breq.datain = valB << 8; 110 | end 111 | 2'b01: begin 112 | breq.writeen = 4'b0100; 113 | breq.datain = valB << 16; 114 | end 115 | 2'b00: begin 116 | breq.writeen = 4'b1000; 117 | breq.datain = valB << 24; 118 | end 119 | endcase 120 | end 121 | endcase 122 | end 123 | tagged Invalid: begin 124 | if (req.width matches tagged Word) begin 125 | $display("%x: DMEM READ REQ, word [%x] ->", req.pc, req.addr); 126 | end else begin 127 | $display("%x: DMEM READ REQ, other [%x] ->", req.pc, req.addr); 128 | $finish(1); 129 | end 130 | end 131 | endcase 132 | end 133 | if (req.addr < 1024) begin 134 | bram.portB.request.put(breq); 135 | end else begin 136 | delayFIFO.enq(breq); 137 | end 138 | endmethod 139 | endinterface 140 | interface Get response; 141 | method ActionValue#(Word) get(); 142 | waitQ.deq(); 143 | let req = waitQ.first; 144 | Bit#(n) addr = req.addr[nwords+1:2]; 145 | Word res <- bram.portB.response.get(); 146 | if (!req.spurious) begin 147 | if (req.data matches Invalid) begin 148 | $display("%x: DMEM READ RES, word [%x] -> %x", req.pc, req.addr, res); 149 | end 150 | end 151 | return res; 152 | endmethod 153 | endinterface 154 | endinterface 155 | endinterface 156 | endmodule 157 | 158 | endpackage 159 | -------------------------------------------------------------------------------- /lanai/Tb.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package Tb; 5 | 6 | import Connectable :: *; 7 | import Wishbone :: *; 8 | import TieOff :: *; 9 | 10 | import Lanai_IFC :: *; 11 | import Lanai_CPU :: *; 12 | import Lanai_Memory :: *; 13 | import WishboneCrossbar :: *; 14 | import WishboneSPI :: *; 15 | 16 | function Maybe#(WishboneCrossbar::DecodedAddr#(1, 32)) decoder(Bit#(32) address); 17 | return case (address) matches 18 | 32'h4001_3???: tagged Valid DecodedAddr { downstream: 0, address: address & 32'hfff }; 19 | default: tagged Invalid; 20 | endcase; 21 | endfunction 22 | 23 | (* synthesize *) 24 | module mkTb (Empty); 25 | Lanai_BlockRAM#(4096) bram <- mkBlockMemory("lanai/bram.bin"); 26 | Lanai_IFC cpu <- mkLanaiCPU; 27 | 28 | WishboneCrossbar::Crossbar#(1, 1, 32, 32, 4) fabric <- mkCrossbar(decoder); 29 | WishboneSPI::SPIController#(32) spi <- mkSPIController; 30 | mkConnection(fabric.downstreams[0], spi.slave); 31 | 32 | mkConnection(cpu.imem_client, bram.memory.imem); 33 | mkConnection(cpu.dmem_client, bram.memory.dmem); 34 | mkConnection(cpu.sysmem_client, fabric.upstreams[0]); 35 | 36 | Reg#(int) i <- mkReg(0); 37 | rule testFetch; 38 | if (i > 400) begin 39 | //bram.dump; 40 | $finish(0); 41 | end 42 | i <= i + 1; 43 | //$display("counter:", cpu.readPC); 44 | endrule 45 | endmodule 46 | 47 | endpackage 48 | -------------------------------------------------------------------------------- /lanai/frontend/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//build/bluespec:rules.bzl", "bluespec_library", "bluesim_test") 2 | 3 | bluespec_library( 4 | name = "frontend", 5 | srcs = [ 6 | "LanaiFrontend.bsv", 7 | ], 8 | synthesize = { 9 | "LanaiFrontend": ["mkLanaiFrontend"], 10 | }, 11 | deps = [ 12 | "//lanai", 13 | ], 14 | visibility = ["//visibility:public"], 15 | ) 16 | 17 | 18 | bluespec_library( 19 | name = "spi_flash_controller", 20 | srcs = [ 21 | "SPIFlashController.bsv", 22 | ], 23 | synthesize = { 24 | "SPIFlashController": ["mkTbFlashController"], 25 | }, 26 | deps = [ 27 | "//lanai", 28 | ], 29 | visibility = ["//visibility:public"], 30 | ) 31 | 32 | bluesim_test( 33 | name = "test_spi_flash_controller", 34 | deps = [ 35 | ":spi_flash_controller", 36 | ], 37 | top = "mkTbFlashController", 38 | ) 39 | 40 | -------------------------------------------------------------------------------- /lanai/frontend/LanaiFrontend.bsv: -------------------------------------------------------------------------------- 1 | package LanaiFrontend; 2 | 3 | import GetPut :: *; 4 | import ClientServer :: *; 5 | import FIFO :: *; 6 | import SpecialFIFOs :: *; 7 | 8 | import Lanai_IFC :: *; 9 | 10 | interface LanaiFrontend; 11 | interface Server#(Word, Word) core_imem; 12 | interface Server#(DMemReq, Word) core_dmem; 13 | 14 | interface Client#(Word, Word) fmc_imem; 15 | interface Client#(DMemReq, Word) fmc_dmem; 16 | 17 | interface Client#(Word, Word) ram_imem; 18 | interface Client#(DMemReq, Word) ram_dmem; 19 | endinterface 20 | 21 | typedef enum { 22 | FMC, 23 | RAM 24 | } RoutingDecision; 25 | 26 | typeclass Routable#(type a); 27 | function RoutingDecision route(a req); 28 | endtypeclass 29 | 30 | instance Routable#(Word); 31 | function RoutingDecision route(Word addr); 32 | if (addr >= 'h2000_0000) begin 33 | return RAM; 34 | end else begin 35 | return FMC; 36 | end 37 | endfunction 38 | endinstance 39 | 40 | instance Routable#(DMemReq); 41 | function RoutingDecision route(DMemReq req); 42 | return route(req.addr); 43 | endfunction 44 | endinstance 45 | 46 | interface Fork#(type t); 47 | interface Server#(t, Word) core; 48 | interface Client#(t, Word) fmc; 49 | interface Client#(t, Word) ram; 50 | endinterface 51 | 52 | module mkFork(Fork#(t)) provisos (Routable#(t), Bits#(t, _)); 53 | FIFO#(t) fifoReqFMC <- mkBypassFIFO; 54 | FIFO#(t) fifoReqRAM <- mkBypassFIFO; 55 | FIFO#(Word) fifoRes <- mkBypassFIFO; 56 | 57 | interface Server core; 58 | interface Put request; 59 | method Action put(t req); 60 | case (route(req)) matches 61 | FMC: fifoReqFMC.enq(req); 62 | RAM: fifoReqRAM.enq(req); 63 | endcase 64 | endmethod 65 | endinterface 66 | interface response = fifoToGet(fifoRes); 67 | endinterface 68 | 69 | interface Client fmc; 70 | interface request = fifoToGet(fifoReqFMC); 71 | interface response = fifoToPut(fifoRes); 72 | endinterface 73 | interface Client ram; 74 | interface request = fifoToGet(fifoReqRAM); 75 | interface response = fifoToPut(fifoRes); 76 | endinterface 77 | endmodule 78 | 79 | (* synthesize *) 80 | module mkLanaiFrontend(LanaiFrontend); 81 | Fork#(Word) forkIMem <- mkFork; 82 | Fork#(DMemReq) forkDMem <- mkFork; 83 | 84 | interface core_imem = forkIMem.core; 85 | interface core_dmem = forkDMem.core; 86 | interface fmc_imem = forkIMem.fmc; 87 | interface fmc_dmem = forkDMem.fmc; 88 | interface ram_imem = forkIMem.ram; 89 | interface ram_dmem = forkDMem.ram; 90 | endmodule 91 | 92 | endpackage 93 | -------------------------------------------------------------------------------- /lanai/qasm/BUILD.bazel: -------------------------------------------------------------------------------- 1 | py_binary( 2 | name = "qasm", 3 | srcs = [ 4 | "qasm.py", 5 | ], 6 | visibility = [ 7 | "//visibility:public", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /lanai/qasm/qasm.py: -------------------------------------------------------------------------------- 1 | # qasm - the quaint lanai assembler. 2 | # 3 | # This is meant to be used for testing only. 4 | 5 | import math 6 | import struct 7 | 8 | def serialize(l): 9 | bs = [] 10 | for (nbits, val) in l: 11 | val = int(val) 12 | if val > 0 and math.log(val, 2) > nbits: 13 | raise Exception('too wide, {} bits, {}', nbits, val) 14 | bs.append(('{:0'+str(nbits)+'b}').format(val)) 15 | return '_'.join(bs) 16 | 17 | def op3bit(opcode): 18 | return { 19 | 'add': 0, 'addc': 1, 'sub': 2, 'subb': 3, 'and': 4, 'or': 5, 'xor': 6, 20 | 'sh': 7, 'sha': 7, 21 | }[opcode] 22 | 23 | def op7bit(opcode): 24 | if opcode == 'sh': 25 | return 0b11110000 26 | if opcode == 'sha': 27 | return 0b11111000 28 | if opcode == 'sel': 29 | return 0b11100000 30 | return op3bit(opcode) << 5 31 | 32 | def ri(opcode, rd, rs, flags, const): 33 | high = False 34 | if opcode in ['sh', 'sha']: 35 | if const > 31 or const < -31: 36 | raise Exception('invalid shift amount') 37 | const, = struct.unpack('>H', struct.pack('>h', const)) 38 | high = opcode == 'sha' 39 | 40 | else: 41 | if (const & 0xffff) == 0: 42 | high = True 43 | const = const >> 16 44 | else: 45 | if (const >> 16) != 0: 46 | raise Exception("invalid constant {:032x}".format(const)) 47 | return serialize([ 48 | (1, 0), 49 | (3, op3bit(opcode)), 50 | (5, rd), 51 | (5, rs), 52 | (1, flags), 53 | (1, high), 54 | (16, const), 55 | ]) 56 | 57 | def rr(opcode, rd, rs1, flags, rs2, condition): 58 | return serialize([ 59 | (4, 0b1100), 60 | (5, rd), 61 | (5, rs1), 62 | (1, flags), 63 | (1, condition & 1), 64 | (5, rs2), 65 | (8, op7bit(opcode)), 66 | (3, condition >> 1), 67 | ]) 68 | 69 | def rm(store, rd, rs1, p, q, constant): 70 | return serialize([ 71 | (3, 0b100), 72 | (1, store), 73 | (5, rd), 74 | (5, rs1), 75 | (1, p), 76 | (1, q), 77 | (16, constant), 78 | ]) 79 | 80 | def regno(name): 81 | val = { 82 | 'pc': 2, 'sw': 3, 'rv': 8, 'rr1': 10, 'rr2': 11, 83 | }.get(name) 84 | if val is not None: 85 | return val 86 | return int(name[1:]) 87 | 88 | def condno(name): 89 | return { 90 | 't': 0, 'f': 1, 'hi': 2, 'ls': 3, 'cc': 4, 'cs': 5, 'ne': 6, 'eq': 7, 91 | 'vc': 8, 'vs': 9, 'pl': 10, 'mi': 11, 'ge': 12, 'lt': 13, 'gt': 14, 92 | 'le': 15 93 | }[name] 94 | 95 | def asm(text): 96 | parts = text.split() 97 | opcode = parts[0] 98 | flags = opcode.endswith('.f') 99 | if flags: 100 | opcode = opcode[:-2] 101 | operands = [p.strip(',') for p in parts[1:]] 102 | registers = [regno(el[1:]) if el.startswith('%') else None for el in operands] 103 | imms = [] 104 | for el in operands: 105 | try: 106 | val = int(el) 107 | except ValueError: 108 | try: 109 | val = int(el, 16) 110 | except ValueError: 111 | val = None 112 | imms.append(val) 113 | 114 | if opcode in ['add', 'addc', 'sub', 'subb', 'and', 'or', 'xor', 'sh', 'sha']: 115 | if len(operands) != 3: 116 | raise Exception('invalid operand count {}'.format(text)) 117 | [src1, src2, dest] = registers 118 | if dest is None: 119 | raise Exception('destination must be register') 120 | if src1 is None: 121 | raise Exception('source must be register') 122 | if src2 is None: 123 | return ri(opcode, dest, src1, flags, imms[1]) 124 | else: 125 | return rr(opcode, dest, src1, flags, src2, 0) 126 | 127 | if opcode.startswith('sel.'): 128 | cond = opcode.split('.')[1] 129 | [src1, src2, dest] = registers 130 | if dest is None: 131 | raise Exception('destination must be register') 132 | if src1 is None: 133 | raise Exception('source1 must be register') 134 | if src2 is None: 135 | raise Exception('source2 must be register') 136 | return rr('sel', dest, src1, flags, src2, condno(cond)) 137 | 138 | if opcode in ['ld', 'st']: 139 | if len(operands) != 2: 140 | raise Exception('invalid operand count {}'.format(text)) 141 | mo = 0 142 | ro = 1 143 | if opcode == 'st': 144 | mo = 1 145 | ro = 0 146 | 147 | parts = operands[mo].split('[') 148 | 149 | offs = None 150 | if len(parts) == 1: 151 | offs = 0 152 | else: 153 | offs = parts[0] 154 | parts = parts[1:] 155 | 156 | src = parts[0].split(']')[0] 157 | preincr = False 158 | postincr = False 159 | if src.endswith('++'): 160 | postincr = True 161 | offs = 4 162 | src = src[:-2] 163 | if src.endswith('*'): 164 | postincr = True 165 | src = src[:-1] 166 | if src.startswith('++'): 167 | preincr = True 168 | offs = 4 169 | src = src[2:] 170 | if src.startswith('*'): 171 | preincr = True 172 | src = src[1:] 173 | 174 | dest = registers[ro] 175 | offs = int(offs) 176 | src = regno(src[1:]) 177 | 178 | p = False 179 | q = False 180 | if offs != 0: 181 | if preincr: 182 | p = True 183 | q = True 184 | elif postincr: 185 | q = True 186 | else: 187 | p = True 188 | 189 | return rm(opcode == 'st', dest, src, p, q, offs) 190 | 191 | 192 | max_counter = int(25e6/7) 193 | #max_counter = 5 194 | 195 | insns = { 196 | #0: 'add %r0, {}, %r16'.format(max_counter & 0xffff), 197 | #4: 'or %r16, {}, %r16'.format(max_counter & 0xffff0000), 198 | #8: 'add %r0, 0, %r7', 199 | #12: 'add %r0, 0, %r4', 200 | ##16: 'add %r0, 0xf000, %r9', 201 | 202 | ##32: 'ld 0[%r9], %r14', 203 | ##36: 'ld [%r9++], %r14', 204 | ##40: 'ld 4[%r9], %r14', 205 | ##44: 'ld [++%r9], %r14', 206 | 207 | ##64: 'st %r14, 0[%r9]', 208 | ##68: 'st %r14, [%r9++]', 209 | ##72: 'st %r14, 4[%r9]', 210 | ##76: 'st %r14, [++%r9]', 211 | 212 | #16: 'add %r4, 1, %r4', 213 | #20: 'add %r7, 1, %r17', 214 | #24: 'sub.f %r4, %r16, %r10', 215 | 216 | #28: 'sel.pl %r17, %r7, %r7', 217 | #32: 'sel.pl %r0, %r4, %r4', 218 | #36: 'add %r0, 0x10, %pc', 219 | 220 | #0: 'add %r0, {}, %r16'.format(max_counter & 0xffff), 221 | #4: 'or %r16, {}, %r16'.format(max_counter & 0xffff0000), 222 | #8: 'add %r0, 0, %r7', 223 | #12: 'add %r0, 0, %r4', 224 | 225 | #16: 'add %r4, 1, %r4', 226 | #20: 'add %r7, 1, %r17', 227 | #24: 'sub.f %r4, %r16, %r0', 228 | 229 | #28: 'sel.eq %r17, %r7, %r7', 230 | #32: 'sel.eq %r0, %r4, %r4', 231 | #36: 'add %r0, 0x10, %pc', 232 | 233 | 0: 'add %r0, 0xbee0, %r10', 234 | 4: 'add %r0, 256, %r9', 235 | 8: 'st %r10, 0[%r9]', 236 | 12: 'ld 0[%r9++], %r11', 237 | 16: 'add %r11, 1, %r7', 238 | 20: 'add %r11, 2, %r7', 239 | 24: 'add %r11, 3, %r7', 240 | 28: 'add %r11, 4, %r7', 241 | 242 | 60: 'add %r0, 0, %r11', 243 | 64: 'add %r0, 512, %r9', 244 | 68: 'st %r10, 0[%r9]', 245 | 72: 'ld 0[%r9++], %r11', 246 | 76: 'add %r11, 1, %r7', 247 | 80: 'add %r11, 2, %r7', 248 | 84: 'add %r11, 3, %r7', 249 | 88: 'add %r11, 4, %r7', 250 | 251 | 256: 'add %r0, 128, %pc', 252 | } 253 | 254 | ram = [0 for i in range(2**15)] 255 | 256 | for addr, ins in insns.items(): 257 | ins = int(asm(ins).replace('_', ''), 2) 258 | ram[addr+0] = (ins >> 24 ) & 0xff 259 | ram[addr+1] = (ins >> 16 ) & 0xff 260 | ram[addr+2] = (ins >> 8 ) & 0xff 261 | ram[addr+3] = (ins >> 0 ) & 0xff 262 | 263 | import sys 264 | with open(sys.argv[1], 'w') as f: 265 | for i in range(0, len(ram), 4): 266 | el = ram[i+0] << 24 267 | el |= ram[i+1] << 16 268 | el |= ram[i+2] << 8 269 | el |= ram[i+3] 270 | f.write('{:08x}\n'.format(el)) 271 | 272 | #with open('bram.bin', 'wb') as f: 273 | # for i in range(0, len(ram)): 274 | # f.write(bytes([ram[i]])) 275 | -------------------------------------------------------------------------------- /lanai/test.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | # Run this manually to regenerate bram.bin. 5 | # TODO: pipe this into Bazel, or just use a real assembler at some point. 6 | 7 | import math 8 | import struct 9 | 10 | def serialize(l): 11 | bs = [] 12 | for (nbits, val) in l: 13 | val = int(val) 14 | if val > 0 and math.log(val, 2) > nbits: 15 | raise Exception('too wide, {} bits, {}', nbits, val) 16 | bs.append(('{:0'+str(nbits)+'b}').format(val)) 17 | return '_'.join(bs) 18 | 19 | def op3bit(opcode): 20 | return { 21 | 'add': 0, 'addc': 1, 'sub': 2, 'subb': 3, 'and': 4, 'or': 5, 'xor': 6, 22 | 'sh': 7, 'sha': 7, 23 | }[opcode] 24 | 25 | def op7bit(opcode): 26 | if opcode == 'sh': 27 | return 0b11110000 28 | if opcode == 'sha': 29 | return 0b11111000 30 | if opcode == 'sel': 31 | return 0b11100000 32 | return op3bit(opcode) << 5 33 | 34 | def ri(opcode, rd, rs, flags, const): 35 | high = False 36 | if opcode in ['sh', 'sha']: 37 | if const > 31 or const < -31: 38 | raise Exception('invalid shift amount') 39 | const, = struct.unpack('>H', struct.pack('>h', const)) 40 | high = opcode == 'sha' 41 | 42 | else: 43 | if (const & 0xffff) == 0: 44 | high = True 45 | const = const >> 16 46 | else: 47 | if (const >> 16) != 0: 48 | raise Exception("invalid constant {:032x}".format(const)) 49 | return serialize([ 50 | (1, 0), 51 | (3, op3bit(opcode)), 52 | (5, rd), 53 | (5, rs), 54 | (1, flags), 55 | (1, high), 56 | (16, const), 57 | ]) 58 | 59 | def rr(opcode, rd, rs1, flags, rs2, condition): 60 | return serialize([ 61 | (4, 0b1100), 62 | (5, rd), 63 | (5, rs1), 64 | (1, flags), 65 | (1, condition & 1), 66 | (5, rs2), 67 | (8, op7bit(opcode)), 68 | (3, condition >> 1), 69 | ]) 70 | 71 | def rm(store, rd, rs1, p, q, constant): 72 | return serialize([ 73 | (3, 0b100), 74 | (1, store), 75 | (5, rd), 76 | (5, rs1), 77 | (1, p), 78 | (1, q), 79 | (16, constant), 80 | ]) 81 | 82 | def regno(name): 83 | val = { 84 | 'pc': 2, 'sw': 3, 'rv': 8, 'rr1': 10, 'rr2': 11, 85 | }.get(name) 86 | if val is not None: 87 | return val 88 | return int(name[1:]) 89 | 90 | def condno(name): 91 | return { 92 | 't': 0, 'f': 1, 'hi': 2, 'ls': 3, 'cc': 4, 'cs': 5, 'ne': 6, 'eq': 7, 93 | 'vc': 8, 'vs': 9, 'pl': 10, 'mi': 11, 'ge': 12, 'lt': 13, 'gt': 14, 94 | 'le': 15 95 | }[name] 96 | 97 | def asm(text): 98 | parts = text.split() 99 | opcode = parts[0] 100 | flags = opcode.endswith('.f') 101 | if flags: 102 | opcode = opcode[:-2] 103 | operands = [p.strip(',') for p in parts[1:]] 104 | registers = [regno(el[1:]) if el.startswith('%') else None for el in operands] 105 | imms = [] 106 | for el in operands: 107 | try: 108 | val = int(el) 109 | except ValueError: 110 | try: 111 | val = int(el, 16) 112 | except ValueError: 113 | val = None 114 | imms.append(val) 115 | 116 | if opcode in ['add', 'addc', 'sub', 'subb', 'and', 'or', 'xor', 'sh', 'sha']: 117 | if len(operands) != 3: 118 | raise Exception('invalid operand count {}'.format(text)) 119 | [src1, src2, dest] = registers 120 | if dest is None: 121 | raise Exception('destination must be register') 122 | if src1 is None: 123 | raise Exception('source must be register') 124 | if src2 is None: 125 | return ri(opcode, dest, src1, flags, imms[1]) 126 | else: 127 | return rr(opcode, dest, src1, flags, src2, 0) 128 | 129 | if opcode.startswith('sel.'): 130 | cond = opcode.split('.')[1] 131 | [src1, src2, dest] = registers 132 | if dest is None: 133 | raise Exception('destination must be register') 134 | if src1 is None: 135 | raise Exception('source1 must be register') 136 | if src2 is None: 137 | raise Exception('source2 must be register') 138 | return rr('sel', dest, src1, flags, src2, condno(cond)) 139 | 140 | if opcode in ['ld', 'st']: 141 | if len(operands) != 2: 142 | raise Exception('invalid operand count {}'.format(text)) 143 | mo = 0 144 | ro = 1 145 | if opcode == 'st': 146 | mo = 1 147 | ro = 0 148 | 149 | parts = operands[mo].split('[') 150 | 151 | offs = None 152 | if len(parts) == 1: 153 | offs = 0 154 | else: 155 | offs = parts[0] 156 | parts = parts[1:] 157 | 158 | src = parts[0].split(']')[0] 159 | preincr = False 160 | postincr = False 161 | if src.endswith('++'): 162 | postincr = True 163 | offs = 4 164 | src = src[:-2] 165 | if src.endswith('*'): 166 | postincr = True 167 | src = src[:-1] 168 | if src.startswith('++'): 169 | preincr = True 170 | offs = 4 171 | src = src[2:] 172 | if src.startswith('*'): 173 | preincr = True 174 | src = src[1:] 175 | 176 | dest = registers[ro] 177 | offs = int(offs) 178 | src = regno(src[1:]) 179 | 180 | p = False 181 | q = False 182 | if offs != 0: 183 | if preincr: 184 | p = True 185 | q = True 186 | elif postincr: 187 | q = True 188 | else: 189 | p = True 190 | 191 | return rm(opcode == 'st', dest, src, p, q, offs) 192 | 193 | 194 | max_counter = int(25e6/7) 195 | #max_counter = 5 196 | 197 | insns = { 198 | #0: 'add %r0, {}, %r16'.format(max_counter & 0xffff), 199 | #4: 'or %r16, {}, %r16'.format(max_counter & 0xffff0000), 200 | #8: 'add %r0, 0, %r7', 201 | #12: 'add %r0, 0, %r4', 202 | ##16: 'add %r0, 0xf000, %r9', 203 | 204 | ##32: 'ld 0[%r9], %r14', 205 | ##36: 'ld [%r9++], %r14', 206 | ##40: 'ld 4[%r9], %r14', 207 | ##44: 'ld [++%r9], %r14', 208 | 209 | ##64: 'st %r14, 0[%r9]', 210 | ##68: 'st %r14, [%r9++]', 211 | ##72: 'st %r14, 4[%r9]', 212 | ##76: 'st %r14, [++%r9]', 213 | 214 | #16: 'add %r4, 1, %r4', 215 | #20: 'add %r7, 1, %r17', 216 | #24: 'sub.f %r4, %r16, %r10', 217 | 218 | #28: 'sel.pl %r17, %r7, %r7', 219 | #32: 'sel.pl %r0, %r4, %r4', 220 | #36: 'add %r0, 0x10, %pc', 221 | 222 | #0: 'add %r0, {}, %r16'.format(max_counter & 0xffff), 223 | #4: 'or %r16, {}, %r16'.format(max_counter & 0xffff0000), 224 | #8: 'add %r0, 0, %r7', 225 | #12: 'add %r0, 0, %r4', 226 | 227 | #16: 'add %r4, 1, %r4', 228 | #20: 'add %r7, 1, %r17', 229 | #24: 'sub.f %r4, %r16, %r0', 230 | 231 | #28: 'sel.eq %r17, %r7, %r7', 232 | #32: 'sel.eq %r0, %r4, %r4', 233 | #36: 'add %r0, 0x10, %pc', 234 | 235 | 0: 'add %r0, 0xbee0, %r10', 236 | 4: 'add %r0, 256, %r9', 237 | 8: 'st %r10, 0[%r9]', 238 | 12: 'ld 0[%r9++], %r11', 239 | 16: 'add %r11, 1, %r7', 240 | 20: 'add %r11, 2, %r7', 241 | 24: 'add %r11, 3, %r7', 242 | 28: 'add %r11, 4, %r7', 243 | 244 | 60: 'add %r0, 0, %r11', 245 | 64: 'add %r0, 512, %r9', 246 | 68: 'st %r10, 0[%r9]', 247 | 72: 'ld 0[%r9++], %r11', 248 | 76: 'add %r11, 1, %r7', 249 | 80: 'add %r11, 2, %r7', 250 | 84: 'add %r11, 3, %r7', 251 | 88: 'add %r11, 4, %r7', 252 | 253 | 256: 'add %r0, 128, %pc', 254 | } 255 | 256 | ram = [0 for i in range(2**15)] 257 | 258 | for addr, ins in insns.items(): 259 | ins = int(asm(ins).replace('_', ''), 2) 260 | ram[addr+0] = (ins >> 24 ) & 0xff 261 | ram[addr+1] = (ins >> 16 ) & 0xff 262 | ram[addr+2] = (ins >> 8 ) & 0xff 263 | ram[addr+3] = (ins >> 0 ) & 0xff 264 | 265 | with open('boards/ulx3s/bram.bin', 'w') as f: 266 | for i in range(0, len(ram), 4): 267 | el = ram[i+0] << 24 268 | el |= ram[i+1] << 16 269 | el |= ram[i+2] << 8 270 | el |= ram[i+3] 271 | f.write('{:08x}\n'.format(el)) 272 | with open('lanai/bram.bin', 'w') as f: 273 | for i in range(0, len(ram), 4): 274 | el = ram[i+0] << 24 275 | el |= ram[i+1] << 16 276 | el |= ram[i+2] << 8 277 | el |= ram[i+3] 278 | f.write('{:08x}\n'.format(el)) 279 | with open('bram.bin', 'wb') as f: 280 | for i in range(0, len(ram)): 281 | f.write(bytes([ram[i]])) 282 | -------------------------------------------------------------------------------- /systems/qf100/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//build/bluespec:rules.bzl", "bluespec_library", "bluesim_test") 2 | load("//build/synthesis:rules.bzl", "rtl_bundle") 3 | 4 | bluespec_library( 5 | name = "sky130_sram", 6 | srcs = [ 7 | "Sky130SRAM.bsv", 8 | ], 9 | deps = [ 10 | "//lanai", 11 | ], 12 | visibility = [ "//visibility:public" ], 13 | ) 14 | 15 | bluespec_library( 16 | name = "qf100", 17 | srcs = [ 18 | "QF100.bsv", 19 | ], 20 | deps = [ 21 | "//lanai:cpu", 22 | "//lanai/frontend", 23 | "//lanai/frontend:spi_flash_controller", 24 | "//wishbone/peripherals:spi", 25 | "//wishbone/peripherals:gpio", 26 | "//wishbone/peripherals:kitchen_sink", 27 | ], 28 | synthesize = { 29 | "QF100": [ 30 | "mkQF100BlockRAM", 31 | "mkQF100SPI", 32 | "mkQF100GPIO", 33 | "mkQF100KSC", 34 | "mkQF100Fabric", 35 | "mkQF100FlashController", 36 | ], 37 | }, 38 | visibility = [ "//visibility:public" ], 39 | ) 40 | 41 | bluespec_library( 42 | name = "Tb", 43 | srcs = [ 44 | "SPIFlashEmulator.bsv", 45 | "Tb.bsv", 46 | ], 47 | data = [ 48 | ":flash.bin", 49 | ], 50 | deps = [ 51 | ":qf100", 52 | ":sky130_sram", 53 | ], 54 | synthesize = { 55 | "Tb": [ 56 | "mkTbQF100", 57 | ], 58 | }, 59 | ) 60 | 61 | bluesim_test( 62 | name = "test_qf100", 63 | deps = [ 64 | ":Tb", 65 | ], 66 | top = "mkTbQF100", 67 | ) 68 | 69 | 70 | bluespec_library( 71 | name = "spi_flash_emulator", 72 | srcs = [ 73 | "SPIFlashEmulator.bsv", 74 | ], 75 | visibility = [ "//visibility:public" ], 76 | ) 77 | -------------------------------------------------------------------------------- /systems/qf100/QF100.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package QF100; 5 | 6 | import GetPut :: *; 7 | import ClientServer :: *; 8 | import Connectable :: *; 9 | import Lanai_IFC :: *; 10 | import Lanai_CPU :: *; 11 | import Lanai_Memory :: *; 12 | import LanaiFrontend :: *; 13 | import SPIFlashController :: *; 14 | import RAM :: *; 15 | import WishboneCrossbar :: *; 16 | import WishboneSPI :: *; 17 | import WishboneGPIO :: *; 18 | import WishboneKitchenSink :: *; 19 | 20 | 21 | Bit#(2) wbAddrSPI = 0; 22 | Bit#(2) wbAddrGPIO = 1; 23 | Bit#(2) wbAddrKSC = 2; 24 | 25 | function Maybe#(WishboneCrossbar::DecodedAddr#(3, 32)) decoder(Bit#(32) address); 26 | return case (address) matches 27 | 32'h4001_30??: tagged Valid DecodedAddr { downstream: wbAddrSPI, address: address & 32'hff }; 28 | 32'h4001_08??: tagged Valid DecodedAddr { downstream: wbAddrGPIO, address: address & 32'hff }; 29 | 32'h4001_1c??: tagged Valid DecodedAddr { downstream: wbAddrKSC, address: address & 32'hff }; 30 | default: tagged Invalid; 31 | endcase; 32 | endfunction 33 | 34 | (* synthesize *) 35 | module mkQF100BlockRAM(Lanai_BlockRAM#(2048)); 36 | Lanai_BlockRAM#(2048) inner <- mkBlockMemory(tagged Invalid); 37 | return inner; 38 | endmodule 39 | 40 | (* synthesize *) 41 | module mkQF100SPI(WishboneSPI::SPIController#(32)); 42 | let res <- mkSPIController; 43 | interface slave = res.slave; 44 | interface spiMaster = res.spiMaster; 45 | endmodule 46 | 47 | (* synthesize *) 48 | module mkQF100GPIO(WishboneGPIO::GPIOController#(32)); 49 | let res <- mkGPIOController; 50 | interface slave = res.slave; 51 | method in = res.in; 52 | method out = res.out; 53 | method oe = res.oe; 54 | endmodule 55 | 56 | (* synthesize *) 57 | module mkQF100KSC(WishboneKitchenSink::KitchenSinkController#(32)); 58 | let res <- mkKitchenSinkController; 59 | interface slave = res.slave; 60 | endmodule 61 | 62 | interface QF100Fabric; 63 | interface Wishbone::Slave#(32, 32, 4) cpu; 64 | interface Wishbone::Master#(32, 32, 4) spi; 65 | interface Wishbone::Master#(32, 32, 4) gpio; 66 | interface Wishbone::Master#(32, 32, 4) ksc; 67 | endinterface 68 | 69 | (* synthesize *) 70 | module mkQF100Fabric(QF100Fabric); 71 | WishboneCrossbar::Crossbar#(1, 3, 32, 32, 4) fabric <- mkCrossbar(decoder); 72 | 73 | interface cpu = fabric.upstreams[0]; 74 | interface spi = fabric.downstreams[wbAddrSPI]; 75 | interface gpio = fabric.downstreams[wbAddrGPIO]; 76 | interface ksc = fabric.downstreams[wbAddrKSC]; 77 | endmodule 78 | 79 | (* synthesize *) 80 | module mkQF100FlashController(SPIFlashController#(8, 4)); 81 | SPIFlashController#(8, 4) fmc <- mkSPIFlashController; 82 | return fmc; 83 | endmodule 84 | 85 | interface QF100; 86 | // RAM. 87 | interface Client#(Word, Word) ram_imem; 88 | interface Client#(DMemReq, Word) ram_dmem; 89 | 90 | // Memory SPI. 91 | interface WishboneSPI::Master mspi; 92 | (* always_ready *) 93 | method Bool mspi_csb; 94 | 95 | // General purpose SPI. 96 | interface WishboneSPI::Master spi; 97 | 98 | // GPIO. 99 | (* always_ready *) 100 | method Bit#(16) gpio_oe; 101 | (* always_ready *) 102 | method Bit#(16) gpio_out; 103 | (* always_ready, always_enabled *) 104 | method Action gpio_in(Bit#(16) value); 105 | endinterface 106 | 107 | module mkQF100(QF100); 108 | LanaiFrontend frontend <- mkLanaiFrontend; 109 | 110 | Lanai_IFC cpu <- mkLanaiCPU; 111 | mkConnection(cpu.imem_client, frontend.core_imem); 112 | mkConnection(cpu.dmem_client, frontend.core_dmem); 113 | 114 | SPIFlashController#(8, 4) fmc <- mkQF100FlashController; 115 | mkConnection(frontend.fmc_imem, fmc.serverA); 116 | rule fmcDMemTranslate; 117 | let req <- frontend.fmc_dmem.request.get(); 118 | fmc.serverB.request.put(req.addr); 119 | endrule 120 | mkConnection(frontend.fmc_dmem.response, fmc.serverB.response); 121 | 122 | QF100Fabric fabric <- mkQF100Fabric; 123 | mkConnection(cpu.sysmem_client, fabric.cpu); 124 | 125 | WishboneSPI::SPIController#(32) spiCtrl <- mkQF100SPI; 126 | mkConnection(fabric.spi, spiCtrl.slave); 127 | 128 | WishboneGPIO::GPIOController#(32) gpioCtrl <- mkQF100GPIO; 129 | mkConnection(fabric.gpio, gpioCtrl.slave); 130 | 131 | WishboneKitchenSink::KitchenSinkController#(32) ksCtrl <- mkQF100KSC; 132 | mkConnection(fabric.ksc, ksCtrl.slave); 133 | 134 | interface mspi = fmc.spi; 135 | method Bool mspi_csb; 136 | return unpack(fmc.csb); 137 | endmethod 138 | interface spi = spiCtrl.spiMaster; 139 | method gpio_oe = gpioCtrl.oe; 140 | method gpio_out = gpioCtrl.out; 141 | method gpio_in = gpioCtrl.in; 142 | interface ram_imem = frontend.ram_imem; 143 | interface ram_dmem = frontend.ram_dmem; 144 | endmodule 145 | 146 | endpackage 147 | -------------------------------------------------------------------------------- /systems/qf100/SPIFlashEmulator.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package SPIFlashEmulator; 5 | 6 | import BRAM :: *; 7 | import FIFO :: *; 8 | import SpecialFIFOs :: *; 9 | import StmtFSM :: *; 10 | 11 | interface SPIFlashEmulator; 12 | (* always_enabled, always_ready *) 13 | method Action mosi(Bool value); 14 | (* always_enabled *) 15 | method Bool miso; 16 | (* always_enabled, always_ready *) 17 | method Action sclk(Bool value); 18 | (* always_enabled, always_ready *) 19 | method Action csb(Bool value); 20 | endinterface 21 | 22 | module mkSPIFlashEmulator#(String filename) (SPIFlashEmulator); 23 | BRAM_Configure cfg = defaultValue; 24 | cfg.latency = 1; 25 | cfg.loadFormat = tagged Hex filename; 26 | cfg.outFIFODepth = 3; 27 | cfg.allowWriteResponseBypass = False; 28 | BRAM1Port#(Bit#(12), Bit#(32)) bram <- mkBRAM1Server(cfg); 29 | 30 | Reg#(Bool) csbReg <- mkReg(False); 31 | Reg#(Bool) sclkReg <- mkReg(False); 32 | Reg#(Bool) mosiReg <- mkReg(False); 33 | 34 | Reg#(Bit#(8)) incomingByte <- mkReg(0); 35 | Reg#(Maybe#(Bit#(3))) bitNo <- mkReg(tagged Invalid); 36 | 37 | rule startReceive(bitNo matches tagged Invalid &&& csbReg == False); 38 | bitNo <= tagged Valid 0; 39 | endrule 40 | 41 | Reg#(Bool) prevSclk <- mkReg(False); 42 | rule updatePrevSclk; 43 | prevSclk <= sclkReg; 44 | endrule 45 | 46 | FIFO#(Maybe#(Bit#(8))) pendingByte <- mkPipelineFIFO; 47 | 48 | Bool clockRisen = prevSclk == False && sclkReg == True; 49 | 50 | rule onClockRisen(bitNo matches tagged Valid .bn &&& csbReg == False &&& clockRisen); 51 | let val = incomingByte; 52 | val[7-bn] = pack(mosiReg); 53 | 54 | if (bn == 7) begin 55 | pendingByte.enq(tagged Valid val); 56 | bitNo <= tagged Invalid; 57 | end else begin 58 | incomingByte <= val; 59 | bitNo <= tagged Valid (bn + 1); 60 | end 61 | endrule 62 | 63 | rule onCSBHigh(bitNo matches tagged Valid .bn &&& csbReg == True); 64 | pendingByte.enq(tagged Invalid); 65 | bitNo <= tagged Invalid; 66 | endrule 67 | 68 | Reg#(Bit#(8)) command <- mkReg(0); 69 | Reg#(Bool) failed <- mkReg(False); 70 | Reg#(Bit#(24)) readAddr <- mkReg(0); 71 | Reg#(Bit#(24)) readAddrWait <- mkReg(0); 72 | Reg#(Bit#(32)) bramBuf <- mkReg(0); 73 | Reg#(Bit#(24)) bramAddr <- mkReg('h1); 74 | Reg#(Bit#(8)) sending <- mkReg(0); 75 | Stmt fsm = seq 76 | while (True) seq 77 | failed <= False; 78 | action 79 | pendingByte.deq(); 80 | case (pendingByte.first) matches 81 | tagged Valid .v: command <= v; 82 | tagged Invalid: failed <= True; 83 | endcase 84 | endaction 85 | 86 | if (failed) continue; 87 | 88 | $display("Flash emulator: recv %x", command); 89 | if (command == 3) seq 90 | action 91 | pendingByte.deq(); 92 | case (pendingByte.first) matches 93 | tagged Valid .v: readAddr[23:16] <= v; 94 | tagged Invalid: failed <= True; 95 | endcase 96 | endaction 97 | if (failed) continue; 98 | action 99 | pendingByte.deq(); 100 | case (pendingByte.first) matches 101 | tagged Valid .v: readAddr[15:8] <= v; 102 | tagged Invalid: failed <= True; 103 | endcase 104 | endaction 105 | if (failed) continue; 106 | action 107 | pendingByte.deq(); 108 | case (pendingByte.first) matches 109 | tagged Valid .v: readAddr[7:0] <= v; 110 | tagged Invalid: failed <= True; 111 | endcase 112 | endaction 113 | if (failed) continue; 114 | 115 | par 116 | while (!failed) seq 117 | while (readAddr[1:0] != 0 && failed == False) seq 118 | noAction; 119 | endseq 120 | readAddrWait <= readAddr; 121 | bram.portA.request.put(BRAMRequest { responseOnWrite: True 122 | , address: readAddrWait[13:2] 123 | , datain: 0 124 | , write: False 125 | }); 126 | action 127 | let bramRes <- bram.portA.response.get(); 128 | action 129 | bramBuf <= bramRes; 130 | bramAddr <= readAddrWait; 131 | endaction 132 | endaction 133 | endseq 134 | 135 | seq 136 | while (bramAddr != readAddr) seq 137 | noAction; 138 | endseq 139 | action 140 | let byteInBuf = (7 - (readAddr & 'b111)) << 3; 141 | sending <= bramBuf[byteInBuf+7:byteInBuf]; 142 | endaction 143 | $display("Flash emulator: read addr: %x", readAddr); 144 | while (!failed) seq 145 | action 146 | case (pendingByte.first) matches 147 | tagged Invalid: failed <= True; 148 | endcase 149 | pendingByte.deq(); 150 | endaction 151 | seq 152 | readAddr <= readAddr + 1; 153 | while (bramAddr != (readAddr & (~'b11))) seq 154 | noAction; 155 | endseq 156 | action 157 | let byteInBuf = (7 - (readAddr & 'b111)) << 3; 158 | sending <= bramBuf[byteInBuf+7:byteInBuf]; 159 | endaction 160 | endseq 161 | endseq 162 | $display("Flash emulator: read done"); 163 | endseq 164 | endpar 165 | 166 | endseq else seq 167 | $display("Flash emulator: unhandled command %x", command); 168 | endseq 169 | endseq 170 | endseq; 171 | 172 | mkAutoFSM(fsm); 173 | 174 | method Action mosi(Bool value); 175 | mosiReg <= value; 176 | endmethod 177 | method Action sclk(Bool value); 178 | sclkReg <= value; 179 | endmethod 180 | method Action csb(Bool value); 181 | csbReg <= value; 182 | endmethod 183 | method Bool miso; 184 | return case (bitNo) matches 185 | tagged Valid .bn: unpack(sending[7-bn]); 186 | tagged Invalid: False; 187 | endcase; 188 | endmethod 189 | endmodule 190 | 191 | endpackage 192 | -------------------------------------------------------------------------------- /systems/qf100/Sky130SRAM.bsv: -------------------------------------------------------------------------------- 1 | package Sky130SRAM; 2 | 3 | import FIFO :: *; 4 | import SpecialFIFOs :: *; 5 | import GetPut :: *; 6 | import ClientServer :: *; 7 | import Lanai_Memory :: *; 8 | import Lanai_IFC :: *; 9 | 10 | interface Sky130SRAMCore; 11 | method Action request0(Bit#(9) addr0, Bit#(32) din0, Bool web0, Bit#(4) wmask0); 12 | method Bit#(32) response0; 13 | 14 | method Action request1(Bit#(9) addr1); 15 | method Bit#(32) response1; 16 | endinterface 17 | 18 | import "BVI" sky130_sram_2kbyte_1rw1r_32x512_8_wrapper = 19 | module mkSky130SRAMCore#(Clock clk0, Reset rst0, Clock rclk1, Reset rst1)(Sky130SRAMCore); 20 | default_clock no_clock; 21 | default_reset no_reset; 22 | 23 | input_clock clk0(clk0, (*unused*)clk0_gate) = clk0; 24 | input_reset rsb0() = rst0; 25 | input_reset rsb1() = rst1; 26 | method request0(addr0, din0, web0, wmask0) clocked_by (clk0) enable (cs0); 27 | method dout0 response0 clocked_by (clk0); 28 | 29 | schedule (response0) SB (request0); 30 | 31 | input_clock clk1(clk1, (*unused*)clk1_gate) = clk0; 32 | method request1(addr1) clocked_by (clk1) enable (cs1); 33 | method dout1 response1 clocked_by (clk1); 34 | 35 | schedule (response1) SB (request1); 36 | endmodule 37 | 38 | interface Sky130SRAM; 39 | interface Server#(DMemReq, Word) portA; 40 | interface Server#(Word, Word) portB; 41 | endinterface 42 | 43 | module mkSky130SRAM(Sky130SRAM); 44 | Clock clk <- exposeCurrentClock; 45 | Reset rst <- exposeCurrentReset; 46 | let core <- mkSky130SRAMCore(clk, rst, clk, rst); 47 | 48 | FIFO#(void) inFlight0 <- mkPipelineFIFO; 49 | FIFO#(void) inFlight1 <- mkPipelineFIFO; 50 | 51 | interface Server portA; 52 | interface Put request; 53 | method Action put(DMemReq req); 54 | inFlight0.enq(?); 55 | 56 | Bit#(4) wmask = 0; 57 | Bit#(32) din = 0; 58 | Bool web = False; 59 | if (req.data matches tagged Valid .val) begin 60 | web = True; 61 | case (req.width) matches 62 | tagged Word: begin 63 | //$display("%x: DMEM WRITE REQ, word [%x] <- %x", req.pc, req.addr, val); 64 | wmask = 4'b1111; 65 | din = val; 66 | end 67 | tagged HalfWord: begin 68 | Bit#(32) valH = zeroExtend(val[15:0]); 69 | //$display("%x: DMEM WRITE REQ, hword [%x] <- %x", req.pc, req.addr, valH); 70 | case (req.addr[1]) matches 71 | 1'b1: begin 72 | wmask = 4'b0011; 73 | din = valH; 74 | end 75 | 1'b0: begin 76 | wmask = 4'b1100; 77 | din = valH << 16; 78 | end 79 | endcase 80 | if (req.addr[1] == 1) begin 81 | wmask = 4'b1100; 82 | din = val << 16; 83 | end else begin 84 | wmask = 4'b0011; 85 | din = zeroExtend(val[15:0]); 86 | end 87 | end 88 | tagged Byte: begin 89 | Bit#(32) valB = zeroExtend(val[7:0]); 90 | //$display("%x: DMEM WRITE REQ, byte [%x] <- %x", req.pc, req.addr, valB); 91 | case (req.addr[1:0]) matches 92 | 2'b11: begin 93 | wmask = 4'b0001; 94 | din = valB; 95 | end 96 | 2'b10: begin 97 | wmask = 4'b0010; 98 | din = valB << 8; 99 | end 100 | 2'b01: begin 101 | wmask = 4'b0100; 102 | din = valB << 16; 103 | end 104 | 2'b00: begin 105 | wmask = 4'b1000; 106 | din = valB << 24; 107 | end 108 | endcase 109 | end 110 | endcase 111 | end 112 | 113 | core.request0(req.addr[8:0], din, !web, wmask); 114 | endmethod 115 | endinterface 116 | interface Get response; 117 | method ActionValue#(Bit#(32)) get(); 118 | inFlight0.deq; 119 | 120 | let res = core.response0; 121 | return res; 122 | endmethod 123 | endinterface 124 | endinterface 125 | interface Server portB; 126 | interface Put request; 127 | method Action put(Bit#(32) address); 128 | inFlight1.enq(?); 129 | core.request1(address[8:0]); 130 | endmethod 131 | endinterface 132 | interface Get response; 133 | method ActionValue#(Bit#(32)) get(); 134 | inFlight1.deq; 135 | 136 | let res = core.response1; 137 | return res; 138 | endmethod 139 | endinterface 140 | endinterface 141 | endmodule 142 | 143 | endpackage 144 | -------------------------------------------------------------------------------- /systems/qf100/Tb.bsv: -------------------------------------------------------------------------------- 1 | package Tb; 2 | 3 | import Assert :: *; 4 | import Connectable :: *; 5 | import WishboneSPI :: *; 6 | 7 | import QF100 :: *; 8 | import Lanai_Memory :: *; 9 | import SPIFlashEmulator :: *; 10 | 11 | (* synthesize *) 12 | module mkTbQF100(Empty); 13 | QF100 qf100 <- mkQF100; 14 | Lanai_BlockRAM#(2048) bram <- mkQF100BlockRAM; 15 | SPIFlashEmulator emu <- mkSPIFlashEmulator("systems/qf100/flash.bin"); 16 | 17 | mkConnection(qf100.ram_imem, bram.memory.imem); 18 | mkConnection(qf100.ram_dmem, bram.memory.dmem); 19 | 20 | rule feed_qf100_in; 21 | qf100.gpio_in(0); 22 | qf100.spi.miso(False); 23 | qf100.mspi.miso(emu.miso); 24 | endrule 25 | 26 | rule feed_emu_in; 27 | emu.mosi(unpack(qf100.mspi.mosi)); 28 | emu.sclk(unpack(qf100.mspi.sclk)); 29 | emu.csb(qf100.mspi_csb); 30 | endrule 31 | 32 | Reg#(Bit#(32)) counter <- mkReg(0); 33 | rule upcount; 34 | counter <= counter + 1; 35 | endrule 36 | rule timeout; 37 | dynamicAssert(counter < 40_000, "Timeout."); 38 | endrule 39 | rule findGPIOPatern; 40 | if (qf100.gpio_out == 3) begin 41 | $finish(0); 42 | end 43 | endrule 44 | endmodule 45 | 46 | endpackage 47 | -------------------------------------------------------------------------------- /wishbone/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//build/bluespec:rules.bzl", "bluespec_library", "bluesim_test") 2 | 3 | bluespec_library( 4 | name = "wishbone", 5 | srcs = [ 6 | "Wishbone.bsv", 7 | "WishboneCrossbar.bsv", 8 | ], 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | bluespec_library( 13 | name = "testbenches", 14 | srcs = [ 15 | "TbCBus.bsv", 16 | "TbConnectors.bsv", 17 | "TbCrossbar.bsv", 18 | ], 19 | synthesize = { 20 | "TbCBus": ["mkTbCBus", "mkTbCBusInner"], 21 | "TbConnectors": ["mkTbConnectors"], 22 | "TbCrossbar": ["mkTbCrossbar"], 23 | }, 24 | split_if = True, 25 | deps = [ 26 | ":wishbone", 27 | ], 28 | ) 29 | 30 | bluesim_test( 31 | name = "test_cbus", 32 | deps = [ 33 | ":testbenches", 34 | ], 35 | top = "mkTbCBus", 36 | ) 37 | 38 | bluesim_test( 39 | name = "test_connectors", 40 | deps = [ 41 | ":testbenches", 42 | ], 43 | top = "mkTbConnectors", 44 | ) 45 | 46 | bluesim_test( 47 | name = "test_crossbar", 48 | deps = [ 49 | ":testbenches", 50 | ], 51 | top = "mkTbCrossbar", 52 | ) 53 | -------------------------------------------------------------------------------- /wishbone/TbCBus.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package TbCBus; 5 | 6 | import Assert :: *; 7 | import CBus :: *; 8 | import StmtFSM :: *; 9 | 10 | import Wishbone :: *; 11 | 12 | typedef 24 DCBusAddrWidth; 13 | typedef 32 DCBusDataWidth; 14 | 15 | typedef CBus#(DCBusAddrWidth, DCBusDataWidth) DCBus; 16 | typedef CRAddr#(DCBusAddrWidth, DCBusDataWidth) DCAddr; 17 | typedef ModWithCBus#(DCBusAddrWidth, DCBusDataWidth, i) DModWithCBus#(type i); 18 | 19 | typedef bit TCfgReset; 20 | 21 | typedef Bit#(4) TCfgInit; 22 | typedef Bit#(6) TCfgTz; 23 | typedef Bit#(16) TCfgCnt; 24 | 25 | typedef bit TCfgOnes; 26 | typedef bit TCfgError; 27 | 28 | Bit#(DCBusAddrWidth) cfgResetAddr = 0; 29 | Bit#(DCBusAddrWidth) cfgStateAddr = 1; 30 | Bit#(DCBusAddrWidth) cfgStatusAddr = 2; 31 | 32 | DCAddr cfg_reset_reset = DCAddr { a: cfgResetAddr, o: 0 }; 33 | 34 | DCAddr cfg_setup_init = DCAddr { a: cfgStateAddr, o: 0 }; 35 | DCAddr cfg_setup_tz = DCAddr { a: cfgStateAddr, o: 4 }; 36 | DCAddr cfg_setup_cnt = DCAddr { a: cfgStateAddr, o: 16 }; 37 | 38 | DCAddr cfg_status_ones = DCAddr { a: cfgStatusAddr, o: 0 }; 39 | DCAddr cfg_status_error = DCAddr { a: cfgStatusAddr, o: 1 }; 40 | 41 | interface Block; 42 | endinterface 43 | 44 | module [Module] mkBlock(IWithCBus#(DCBus, Block)); 45 | let ifc <- exposeCBusIFC(mkBlockInternal); 46 | return ifc; 47 | endmodule 48 | 49 | module [DModWithCBus] mkBlockInternal(Block); 50 | Reg#(TCfgReset) reg_reset_reset <- mkCBRegW(cfg_reset_reset, 0); 51 | 52 | Reg#(TCfgInit) reg_setup_init <- mkCBRegRW(cfg_setup_init, 0); 53 | Reg#(TCfgTz) reg_setup_tz <- mkCBRegRW(cfg_setup_tz, 0); 54 | Reg#(TCfgCnt) reg_setup_cnt <- mkCBRegRW(cfg_setup_cnt, 1); 55 | 56 | Reg#(TCfgOnes) reg_status_ones <- mkCBRegRC(cfg_status_ones, 0); 57 | Reg#(TCfgError) reg_status_error <- mkCBRegRC(cfg_status_error, 0); 58 | 59 | rule bumpCounter (reg_setup_cnt != unpack('1) ); 60 | reg_setup_cnt <= reg_setup_cnt + 1; 61 | endrule 62 | 63 | rule watch4ones (reg_setup_cnt == unpack('1)); 64 | reg_status_ones <= 1; 65 | endrule 66 | endmodule 67 | 68 | (* synthesize *) 69 | module mkTbCBusInner(Wishbone::Slave#(32, 24, 4)); 70 | IWithCBus#(DCBus,Block) dut <- mkBlock; 71 | Wishbone::Slave#(32, 24, 4) cbusSlave <- mkCBusBridge(dut.cbus_ifc); 72 | 73 | return cbusSlave; 74 | endmodule 75 | 76 | function Stmt doRead(Wishbone::Slave#(32, 24, 4) slave, Reg#(Bool) ack, Maybe#(Reg#(Bit#(32))) res, Bit#(24) addr); 77 | return seq 78 | slave.request(False, False, 0, 0, 0, False); 79 | while (!ack) seq 80 | par 81 | slave.request(True, True, addr, 0, 4'b1111, False); 82 | action 83 | case (res) matches 84 | tagged Valid .resR: resR <= slave.dat; 85 | endcase 86 | endaction 87 | endpar 88 | endseq 89 | slave.request(False, False, 0, 0, 0, False); 90 | endseq; 91 | endfunction 92 | 93 | function Stmt doWrite(Wishbone::Slave#(32, 24, 4) slave, Reg#(Bool) ack, Bit#(32) data, Bit#(24) addr); 94 | return seq 95 | slave.request(False, False, 0, 0, 0, False); 96 | while (!ack) seq 97 | slave.request(True, True, addr, data, 4'b1111, True); 98 | endseq 99 | slave.request(False, False, 0, 0, 0, False); 100 | endseq; 101 | endfunction 102 | 103 | (* synthesize *) 104 | module mkTbCBus(Empty); 105 | Wishbone::Slave#(32, 24, 4) slave <- mkTbCBusInner; 106 | 107 | Reg#(Bool) ack <- mkReg(False); 108 | Reg#(Bit#(32)) data <- mkReg(0); 109 | rule register; 110 | ack <= slave.ack; 111 | data <= slave.dat; 112 | endrule 113 | 114 | Reg#(Bit#(32)) res <- mkReg(0); 115 | 116 | Stmt test = seq 117 | // Read setup.count and ensure it counts up. 118 | // 119 | // setup.count increments on each cycle of the test core, the following 120 | // values have been made to correspond to practical behaviour of the 121 | // DUT. 122 | doRead(slave, ack, tagged Valid res, 1); 123 | dynamicAssert(res[31:16] == 4, "setup.count should've been 4"); 124 | doRead(slave, ack, tagged Valid res, 1); 125 | dynamicAssert(res[31:16] == 9, "setup.count should've been 9"); 126 | 127 | // Let's write setup.count to a slightly higher value and read it back. 128 | doWrite(slave, ack, 1337 << 16, 1); 129 | doRead(slave, ack, tagged Valid res, 1); 130 | dynamicAssert(res[31:16] == 1340, "setup.count should've been 1337+3"); 131 | endseq; 132 | mkAutoFSM(test); 133 | 134 | endmodule 135 | 136 | endpackage 137 | -------------------------------------------------------------------------------- /wishbone/TbConnectors.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package TbConnectors; 5 | 6 | import Assert :: *; 7 | import StmtFSM :: *; 8 | import Connectable :: *; 9 | import ClientServer :: *; 10 | import GetPut :: *; 11 | 12 | import Wishbone :: *; 13 | 14 | function Action doRead(Wishbone::MasterConnector#(32, 24, 4) master, Bit#(24) addr); 15 | return action 16 | master.server.request.put(SlaveRequest { address: addr 17 | , writeData: tagged Invalid 18 | , select: 4'b1111 19 | }); 20 | endaction; 21 | endfunction 22 | 23 | function Action doWrite(Wishbone::MasterConnector#(32, 24, 4) master, Bit#(24) addr, Bit#(32) data); 24 | return action 25 | master.server.request.put(SlaveRequest { address: addr 26 | , writeData: tagged Valid data 27 | , select: 4'b1111 28 | }); 29 | endaction; 30 | endfunction 31 | 32 | function Action expectResponse(Wishbone::MasterConnector#(32, 24, 4) master, Bit#(32) want, String msg); 33 | return action 34 | let r <- master.server.response.get(); 35 | let v = fromMaybe(0, r.readData); 36 | if (v != want) begin 37 | $display("wanted %x, got %x", want, v); 38 | end 39 | dynamicAssert(v == want, msg); 40 | endaction; 41 | endfunction 42 | 43 | (* synthesize *) 44 | module mkTbConnectors(Empty); 45 | Wishbone::SlaveConnector#(32, 24, 4) slave <- mkAsyncSlaveConnector; 46 | Wishbone::MasterConnector#(32, 24, 4) master <- mkMasterConnector; 47 | 48 | mkConnection(slave.slave, master.master); 49 | 50 | Reg#(Bit#(32)) slaveReg <- mkReg(0); 51 | Reg#(Bit#(32)) delay <- mkReg(0); 52 | 53 | rule delayDown (delay > 0); 54 | delay <= delay - 1; 55 | endrule 56 | rule slaveResponder (delay == 0); 57 | let req <- slave.client.request.get(); 58 | let resp = SlaveResponse { readData: tagged Valid 32'hdeadbeef }; 59 | if (req.address == 1337) begin 60 | case (req.writeData) matches 61 | tagged Invalid: 62 | resp.readData = tagged Valid slaveReg; 63 | tagged Valid .wr: begin 64 | slaveReg <= wr; 65 | end 66 | endcase 67 | end else begin 68 | resp.readData = tagged Valid 32'hdeadbeef; 69 | delay <= zeroExtend(req.address); 70 | end 71 | slave.client.response.put(resp); 72 | endrule 73 | 74 | Reg#(Bit#(32)) i <- mkReg(0); 75 | Stmt test = seq 76 | doRead(master, 0); 77 | expectResponse(master, 32'hdeadbeef, "wanted deadbeef"); 78 | 79 | doRead(master, 1337); 80 | expectResponse(master, 0, "wanted 0"); 81 | doWrite(master, 1337, 10); 82 | expectResponse(master, 0, "wanted 0"); 83 | doRead(master, 1337); 84 | expectResponse(master, 10, "wanted 10"); 85 | 86 | doRead(master, 5); 87 | expectResponse(master, 32'hdeadbeef, "wanted deadbeef"); 88 | doRead(master, 5); 89 | expectResponse(master, 32'hdeadbeef, "wanted deadbeef"); 90 | doRead(master, 0); 91 | expectResponse(master, 32'hdeadbeef, "wanted deadbeef"); 92 | 93 | doRead(master, 0); 94 | par 95 | expectResponse(master, 32'hdeadbeef, "wanted deadbeef"); 96 | doRead(master, 1337); 97 | endpar 98 | expectResponse(master, 10, "wanted 10"); 99 | 100 | for (i <= 0; i < 12; i <= i + 1) seq 101 | noAction; 102 | endseq 103 | endseq; 104 | mkAutoFSM(test); 105 | endmodule 106 | 107 | endpackage 108 | -------------------------------------------------------------------------------- /wishbone/TbCrossbar.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package TbCrossbar; 5 | 6 | import Assert :: *; 7 | import ClientServer :: *; 8 | import Connectable :: *; 9 | import GetPut :: *; 10 | import StmtFSM :: *; 11 | import Vector :: *; 12 | 13 | import Wishbone :: *; 14 | import WishboneCrossbar :: *; 15 | 16 | function Maybe#(WishboneCrossbar::DecodedAddr#(4, 32)) decoder(Bit#(32) addr); 17 | return case (addr) matches 18 | 32'h0???_????: tagged Valid DecodedAddr { downstream: 0, address: addr & 32'hffff }; 19 | 32'h2???_????: tagged Valid DecodedAddr { downstream: 1, address: addr & 32'hffff }; 20 | 32'h4???_????: tagged Valid DecodedAddr { downstream: 2, address: addr & 32'hffff }; 21 | 32'h8???_????: tagged Valid DecodedAddr { downstream: 3, address: addr & 32'hffff }; 22 | default: tagged Invalid; 23 | endcase; 24 | endfunction 25 | 26 | (* synthesize *) 27 | module mkTbCrossbar(Empty); 28 | Vector#(4, Wishbone::MasterConnector#(32, 32, 4)) masters <- replicateM(mkMasterConnector); 29 | Vector#(4, Wishbone::SlaveConnector#(32, 32, 4)) slaves <- replicateM(mkAsyncSlaveConnector); 30 | 31 | WishboneCrossbar::Crossbar#(4, 4, 32, 32, 4) crossbar <- mkCrossbar(decoder); 32 | 33 | for (Integer j = 0; j < 4; j = j + 1) begin 34 | mkConnection(masters[j].master, crossbar.upstreams[j]); 35 | end 36 | for (Integer k = 0; k < 4; k = k + 1) begin 37 | mkConnection(slaves[k].slave, crossbar.downstreams[k]); 38 | end 39 | 40 | for (Integer k = 0; k < 4; k = k + 1) begin 41 | rule respond; 42 | let req <- slaves[k].client.request.get(); 43 | let res = SlaveResponse { readData: tagged Valid req.address }; 44 | slaves[k].client.response.put(res); 45 | endrule 46 | end 47 | 48 | Reg#(Bit#(32)) i <- mkReg(0); 49 | Stmt test = seq 50 | par 51 | masters[0].server.request.put(SlaveRequest { address: 32'h0000_1337 52 | , writeData: tagged Invalid 53 | , select: 4'b1111 54 | }); 55 | masters[1].server.request.put(SlaveRequest { address: 32'h8000_cafe 56 | , writeData: tagged Invalid 57 | , select: 4'b1111 58 | }); 59 | masters[2].server.request.put(SlaveRequest { address: 32'h8000_dead 60 | , writeData: tagged Invalid 61 | , select: 4'b1111 62 | }); 63 | endpar 64 | par 65 | action 66 | let res <- masters[0].server.response.get(); 67 | dynamicAssert(fromMaybe(0, res.readData) == 'h1337, "wanted 0x1337"); 68 | endaction 69 | action 70 | let res <- masters[1].server.response.get(); 71 | dynamicAssert(fromMaybe(0, res.readData) == 'hcafe, "wanted 0xcafe"); 72 | endaction 73 | action 74 | let res <- masters[2].server.response.get(); 75 | dynamicAssert(fromMaybe(0, res.readData) == 'hdead, "wanted 0xdead"); 76 | endaction 77 | endpar 78 | endseq; 79 | mkAutoFSM(test); 80 | endmodule 81 | 82 | endpackage 83 | -------------------------------------------------------------------------------- /wishbone/WishboneCrossbar.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package WishboneCrossbar; 5 | 6 | import ClientServer :: *; 7 | import GetPut :: *; 8 | import FIFO :: *; 9 | import SpecialFIFOs :: *; 10 | import Vector :: *; 11 | import Wishbone :: *; 12 | 13 | interface Crossbar#( numeric type upstreamNum 14 | , numeric type downstreamNum 15 | , numeric type datSize 16 | , numeric type adrSize 17 | , numeric type selSize 18 | ); 19 | interface Vector#(upstreamNum, Wishbone::Slave#(datSize, adrSize, selSize)) upstreams; 20 | interface Vector#(downstreamNum, Wishbone::Master#(datSize, adrSize, selSize)) downstreams; 21 | endinterface 22 | 23 | typedef struct { 24 | Bit#(TLog#(downstreamNum)) downstream; 25 | Bit#(adrSize) address; 26 | } DecodedAddr#(numeric type downstreamNum, numeric type adrSize) deriving (Bits); 27 | 28 | module mkCrossbar#(function Maybe#(DecodedAddr#(downstreamNumer, adrSize)) decoder(Bit#(adrSize) addr)) 29 | (Crossbar#(upstreamNum, downstreamNum, datSize, adrSize, selSize)); 30 | 31 | Vector#(upstreamNum, Wishbone::SlaveConnector#(datSize, adrSize, selSize)) upstreamConnectors <- replicateM(mkAsyncSlaveConnector); 32 | Vector#(downstreamNum, Wishbone::MasterConnector#(datSize, adrSize, selSize)) downstreamConnectors <- replicateM(mkMasterConnector); 33 | 34 | Vector#(upstreamNum, FIFO#(Wishbone::SlaveRequest#(datSize, adrSize, selSize))) upstreamRequests <- replicateM(mkBypassFIFO); 35 | Vector#(downstreamNum, FIFO#(Bit#(TLog#(upstreamNum)))) downstreamPending <- replicateM(mkBypassFIFO); 36 | 37 | function Bool canRoute(Integer u, Integer d); 38 | let req = upstreamRequests[u].first; 39 | let route = decoder(req.address); 40 | return case (route) matches 41 | tagged Invalid: False; 42 | tagged Valid .a: (a.downstream == fromInteger(d)); 43 | endcase; 44 | endfunction 45 | 46 | for (Integer u = 0; u < valueOf(upstreamNum); u = u + 1) begin 47 | rule request_handle; 48 | let req <- upstreamConnectors[u].client.request.get(); 49 | upstreamRequests[u].enq(req); 50 | //$display("Crossbar: req %02d -> ?", u); 51 | endrule 52 | 53 | for (Integer d = 0; d < valueOf(downstreamNum); d = d + 1) begin 54 | rule request_route(canRoute(u, d)); 55 | let req = upstreamRequests[u].first; 56 | let route = decoder(req.address); 57 | case (route) matches 58 | tagged Invalid: begin end 59 | tagged Valid .a: begin 60 | req.address = a.address; 61 | end 62 | endcase 63 | upstreamRequests[u].deq(); 64 | downstreamConnectors[d].server.request.put(req); 65 | downstreamPending[d].enq(fromInteger(u)); 66 | //$display("Crossbar: req %02d -> %02d", u, d); 67 | endrule 68 | end 69 | end 70 | 71 | for (Integer d = 0; d < valueOf(downstreamNum); d = d + 1) begin 72 | for (Integer u = 0; u < valueOf(upstreamNum); u = u + 1) begin 73 | rule response_route(downstreamPending[d].first == fromInteger(u)); 74 | let res <- downstreamConnectors[d].server.response.get(); 75 | downstreamPending[d].deq(); 76 | //$display("Crossbar: res %02d <- %02d", u, d); 77 | upstreamConnectors[u].client.response.put(res); 78 | endrule 79 | end 80 | end 81 | 82 | function Wishbone::Slave#(datSize, adrSize, selSize) getUpstream(Integer j); 83 | return upstreamConnectors[j].slave; 84 | endfunction 85 | 86 | function Wishbone::Master#(datSize, adrSize, selSize) getDownstream(Integer j); 87 | return downstreamConnectors[j].master; 88 | endfunction 89 | 90 | interface upstreams = genWith(getUpstream); 91 | interface downstreams = genWith(getDownstream); 92 | endmodule 93 | endpackage 94 | -------------------------------------------------------------------------------- /wishbone/peripherals/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//build/bluespec:rules.bzl", "bluespec_library", "bluesim_test") 2 | 3 | bluespec_library( 4 | name = "spi", 5 | srcs = [ 6 | "WishboneSPI.bsv", 7 | ], 8 | synthesize = { 9 | "WishboneSPI": ["mkTbSPIController"], 10 | }, 11 | deps = [ 12 | "//wishbone", 13 | ], 14 | visibility = ["//visibility:public"], 15 | ) 16 | 17 | bluesim_test( 18 | name = "test_spi", 19 | deps = [ 20 | ":spi", 21 | ], 22 | top = "mkTbSPIController", 23 | ) 24 | 25 | bluespec_library( 26 | name = "gpio", 27 | srcs = [ 28 | "WishboneGPIO.bsv", 29 | ], 30 | synthesize = { 31 | "WishboneGPIO": ["mkTbGPIOController"], 32 | }, 33 | deps = [ 34 | "//wishbone", 35 | ], 36 | visibility = ["//visibility:public"], 37 | ) 38 | 39 | bluesim_test( 40 | name = "test_gpio", 41 | deps = [ 42 | ":gpio", 43 | ], 44 | top = "mkTbGPIOController", 45 | ) 46 | 47 | bluespec_library( 48 | name = "kitchen_sink", 49 | srcs = [ 50 | "WishboneKitchenSink.bsv", 51 | ], 52 | synthesize = { 53 | "WishboneKitchenSink": ["mkTbKitchenSink"], 54 | }, 55 | deps = [ 56 | "//wishbone", 57 | ], 58 | visibility = ["//visibility:public"], 59 | ) 60 | 61 | bluesim_test( 62 | name = "test_kitchen_sink", 63 | deps = [ 64 | ":kitchen_sink", 65 | ], 66 | top = "mkTbKitchenSink", 67 | ) 68 | 69 | -------------------------------------------------------------------------------- /wishbone/peripherals/WishboneGPIO.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package WishboneGPIO; 5 | 6 | import Assert :: *; 7 | import ClientServer :: *; 8 | import Connectable :: *; 9 | import FIFO :: *; 10 | import GetPut :: *; 11 | import SpecialFIFOs :: *; 12 | import StmtFSM :: *; 13 | import Vector :: *; 14 | import Wishbone :: *; 15 | import Probe :: *; 16 | 17 | interface GPIOController#(numeric type wbAddr); 18 | interface Wishbone::Slave#(32, wbAddr, 4) slave; 19 | 20 | (* always_ready, result="oe" *) 21 | method Bit#(16) oe; 22 | (* always_ready, result="out" *) 23 | method Bit#(16) out; 24 | (* always_ready, always_enabled, prefix="" *) 25 | method Action in((* port="in" *) Bit#(16) value); 26 | endinterface 27 | 28 | typedef enum { 29 | INPUT, 30 | OUTPUT10, 31 | OUTPUT2, 32 | OUTPUT50 33 | } Mode deriving (FShow, Eq); 34 | 35 | instance Bits#(Mode, 2); 36 | function Mode unpack(Bit#(2) x); 37 | return case (x) matches 38 | 0: INPUT; 39 | 1: OUTPUT10; 40 | 2: OUTPUT2; 41 | 3: OUTPUT50; 42 | endcase; 43 | endfunction 44 | function Bit#(2) pack(Mode m); 45 | return case (m) matches 46 | INPUT: 0; 47 | OUTPUT10: 1; 48 | OUTPUT2: 2; 49 | OUTPUT50: 3; 50 | endcase; 51 | endfunction 52 | endinstance 53 | 54 | typedef enum { 55 | ANALOG, 56 | FLOATING, 57 | INPUT, 58 | RESERVED 59 | } ControlInput deriving (FShow, Eq); 60 | 61 | instance Bits#(ControlInput, 2); 62 | function ControlInput unpack(Bit#(2) x); 63 | return case (x) matches 64 | 0: ANALOG; 65 | 1: FLOATING; 66 | 2: INPUT; 67 | 3: RESERVED; 68 | endcase; 69 | endfunction 70 | function Bit#(2) pack(ControlInput m); 71 | return case (m) matches 72 | ANALOG: 0; 73 | FLOATING: 1; 74 | INPUT: 2; 75 | RESERVED: 3; 76 | endcase; 77 | endfunction 78 | endinstance 79 | 80 | typedef enum { 81 | GPIOPUSHPULL, 82 | GPIOOPENDRAIN, 83 | AFIOPUSHPULL, 84 | AFIOOPENDRAIN 85 | } ControlOutput deriving (FShow, Eq); 86 | 87 | instance Bits#(ControlOutput, 2); 88 | function ControlOutput unpack(Bit#(2) x); 89 | return case (x) matches 90 | 0: GPIOPUSHPULL; 91 | 1: GPIOOPENDRAIN; 92 | 2: AFIOPUSHPULL; 93 | 3: AFIOOPENDRAIN; 94 | endcase; 95 | endfunction 96 | function Bit#(2) pack(ControlOutput m); 97 | return case (m) matches 98 | GPIOPUSHPULL: 0; 99 | GPIOOPENDRAIN: 1; 100 | AFIOPUSHPULL: 2; 101 | AFIOOPENDRAIN: 3; 102 | endcase; 103 | endfunction 104 | endinstance 105 | 106 | module mkGPIOController (GPIOController#(wbAddr)); 107 | Vector#(16, Reg#(Mode)) modes <- replicateM(mkReg(INPUT)); 108 | Vector#(16, Reg#(Bit#(2))) controls <- replicateM(mkReg(1)); 109 | Reg#(Bit#(16)) sampledInputs <- mkReg(0); 110 | Reg#(Bit#(16)) registeredOutputs <- mkReg(0); 111 | FIFO#(Bit#(32)) fNewCtl0 <- mkBypassFIFO; 112 | FIFO#(Bit#(32)) fNewCtl1 <- mkBypassFIFO; 113 | Wishbone::SlaveConnector#(32, wbAddr, 4) bus <- mkSyncSlaveConnector; 114 | 115 | Vector#(16, Bool) isInput; 116 | for (Integer n = 0; n < 16; n = n + 1) begin 117 | ControlInput ctl = unpack(controls[n]); 118 | isInput[n] = modes[n] == INPUT && (ctl == INPUT || ctl == FLOATING); 119 | end 120 | Vector#(16, Bool) isOutput; 121 | for (Integer n = 0; n < 16; n = n + 1) begin 122 | ControlOutput ctl = unpack(controls[n]); 123 | isOutput[n] = modes[n] != INPUT && ctl == GPIOPUSHPULL; 124 | end 125 | 126 | let probeModes <- mkProbe; 127 | let probeControls <- mkProbe; 128 | let probeIsInput <- mkProbe; 129 | let probeIsOutput <- mkProbe; 130 | rule updateProbes; 131 | Bit#(32) data = 0; 132 | for (Integer i = 0; i < 16; i = i + 1) begin 133 | data[i*2+1:i*2] = pack(modes[i]); 134 | end 135 | probeModes <= data; 136 | 137 | data = 0; 138 | for (Integer i = 0; i < 16; i = i + 1) begin 139 | data[i*2+1:i*2] = controls[i]; 140 | end 141 | probeControls <= data; 142 | 143 | probeIsInput <= isInput; 144 | probeIsOutput <= isOutput; 145 | endrule 146 | 147 | rule applyCtl0; 148 | let d = fNewCtl0.first(); 149 | fNewCtl0.deq(); 150 | 151 | for (Integer i = 0; i < 8; i = i + 1) begin 152 | controls[i] <= d[4*i+3:4*i+2]; 153 | modes[i] <= unpack(d[4*i+1:4*i]); 154 | end 155 | endrule 156 | 157 | rule applyCtl1; 158 | let d = fNewCtl1.first(); 159 | fNewCtl1.deq(); 160 | 161 | for (Integer i = 0; i < 8; i = i + 1) begin 162 | controls[i+8] <= d[4*i+3:4*i+2]; 163 | modes[i+8] <= unpack(d[4*i+1:4*i]); 164 | end 165 | endrule 166 | 167 | rule wbRequest; 168 | let r <- bus.client.request.get(); 169 | let resp = SlaveResponse { readData: tagged Invalid }; 170 | $display("GPIO: wb request", fshow(r)); 171 | 172 | case (r.address) matches 173 | 0: begin 174 | case (r.writeData) matches 175 | tagged Invalid: begin 176 | Bit#(32) ctl0 = 0; 177 | for (Integer i = 0; i < 8; i = i + 1) begin 178 | ctl0[4*i+3:4*i+2] = controls[i]; 179 | ctl0[4*i+1:4*i] = pack(modes[i]); 180 | end 181 | resp.readData = tagged Valid ctl0; 182 | end 183 | tagged Valid .d: begin 184 | fNewCtl0.enq(d); 185 | end 186 | endcase 187 | end 188 | 4: begin 189 | case (r.writeData) matches 190 | tagged Invalid: begin 191 | Bit#(32) ctl1 = 0; 192 | for (Integer i = 0; i < 8; i = i + 1) begin 193 | ctl1[4*i+3:4*i+2] = controls[i+8]; 194 | ctl1[4*i+1:4*i] = pack(modes[i+8]); 195 | end 196 | resp.readData = tagged Valid ctl1; 197 | end 198 | tagged Valid .d: begin 199 | fNewCtl1.enq(d); 200 | end 201 | endcase 202 | end 203 | 8: begin 204 | case (r.writeData) matches 205 | tagged Invalid: begin 206 | Bit#(32) stat = { 16'b0, sampledInputs }; 207 | resp.readData = tagged Valid stat; 208 | end 209 | tagged Valid .d: begin 210 | end 211 | endcase 212 | end 213 | 'hc: begin 214 | case (r.writeData) matches 215 | tagged Invalid: begin 216 | Bit#(32) octl = { 16'b0, registeredOutputs }; 217 | resp.readData = tagged Valid octl; 218 | end 219 | tagged Valid .d: begin 220 | registeredOutputs <= d[15:0]; 221 | end 222 | endcase 223 | end 224 | endcase 225 | bus.client.response.put(resp); 226 | endrule 227 | 228 | method Action in(Bit#(16) value); 229 | Bit#(16) masked = 0; 230 | for (Integer i = 0; i < 16; i = i + 1) begin 231 | if (isInput[i]) begin 232 | masked[i] = value[i]; 233 | end else begin 234 | masked[i] = 0; 235 | end 236 | end 237 | sampledInputs <= masked; 238 | endmethod 239 | 240 | method Bit#(16) oe; 241 | Bit#(16) res; 242 | for (Integer i = 0; i < 16; i = i + 1) begin 243 | res[i] = pack(isOutput[i]); 244 | end 245 | return res; 246 | endmethod 247 | 248 | method Bit#(16) out; 249 | Bit#(16) res; 250 | for (Integer i = 0; i < 16; i = i + 1) begin 251 | res[i] = case (isOutput[i]) matches 252 | False: 0; 253 | True: registeredOutputs[i]; 254 | endcase; 255 | end 256 | return res; 257 | endmethod 258 | 259 | 260 | interface slave = bus.slave; 261 | endmodule 262 | 263 | function Action doRead(Wishbone::MasterConnector#(32, 32, 4) master, Bit#(32) addr); 264 | return action 265 | master.server.request.put(SlaveRequest { address: addr 266 | , writeData: tagged Invalid 267 | , select: 4'b1111 268 | }); 269 | endaction; 270 | endfunction 271 | 272 | function Action doWrite(Wishbone::MasterConnector#(32, 32, 4) master, Bit#(32) addr, Bit#(32) data); 273 | return action 274 | master.server.request.put(SlaveRequest { address: addr 275 | , writeData: tagged Valid data 276 | , select: 4'b1111 277 | }); 278 | endaction; 279 | endfunction 280 | 281 | function Action eatResponse(Wishbone::MasterConnector#(32, 32, 4) master); 282 | action 283 | let _ <- master.server.response.get(); 284 | endaction 285 | endfunction 286 | 287 | function Action getResponse(Wishbone::MasterConnector#(32, 32, 4) master, Reg#(Bit#(32)) data); 288 | action 289 | let d <- master.server.response.get(); 290 | data <= fromMaybe(0, d.readData); 291 | endaction 292 | endfunction 293 | 294 | 295 | (* synthesize *) 296 | module mkTbGPIOController(Empty); 297 | Wishbone::MasterConnector#(32, 32, 4) master <- mkMasterConnector; 298 | GPIOController#(32) controller <- mkGPIOController; 299 | mkConnection(master.master, controller.slave); 300 | 301 | rule feedInput; 302 | controller.in(16'hdead); 303 | endrule 304 | 305 | Reg#(Bit#(32)) tmp <- mkReg(0); 306 | Stmt test = seq 307 | // GPIO[4] output, everything else inputs. 308 | action 309 | Bit#(32) ctl0 = 32'h44444444; 310 | ctl0[19:18] = 0; 311 | ctl0[17:16] = 1; 312 | doWrite(master, 0, ctl0); 313 | endaction 314 | eatResponse(master); 315 | // GPIO[11] output, everything else inputs. 316 | action 317 | Bit#(32) ctl1 = 32'h44444444; 318 | ctl1[15:14] = 0; 319 | ctl1[13:12] = 1; 320 | doWrite(master, 4, ctl1); 321 | endaction 322 | eatResponse(master); 323 | // Write all ones. 324 | doWrite(master, 'hc, 'hffff_ffff); 325 | eatResponse(master); 326 | 327 | // Expect only bits 4 and 11 to be enabled. 328 | dynamicAssert(controller.oe == 'h0000_0810, "expected gpio 4 and 11 to have output enabled"); 329 | // Expect only bits 4 and 11 to be lit. 330 | dynamicAssert(controller.out == 'h0000_0810, "expected gpio 4 and 11 to be lit"); 331 | 332 | // Turn off bit 4. 333 | doRead(master, 'hc); 334 | getResponse(master, tmp); 335 | tmp <= tmp & 'hffff_ffef; 336 | doWrite(master, 'hc, tmp); 337 | eatResponse(master); 338 | 339 | // Expect only bit 11 to be lit. 340 | dynamicAssert(controller.out == 'h0000_0800, "expected gpio 11 to be lit"); 341 | 342 | // Expect inputs to be 8'hdead, but with bit 11 off. 343 | doRead(master, 'h8); 344 | getResponse(master, tmp); 345 | $display("%x", tmp); 346 | dynamicAssert(tmp == 'h0000_d6ad, "expected inputs to be 0xdead with bit 11 off"); 347 | endseq; 348 | mkAutoFSM(test); 349 | endmodule 350 | 351 | endpackage 352 | -------------------------------------------------------------------------------- /wishbone/peripherals/WishboneKitchenSink.bsv: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | // Copyright (C) 2022 Sergiusz Bazanski 3 | 4 | package WishboneKitchenSink; 5 | 6 | import Assert :: *; 7 | import ClientServer :: *; 8 | import Connectable :: *; 9 | import FIFO :: *; 10 | import GetPut :: *; 11 | import Probe :: *; 12 | import SpecialFIFOs :: *; 13 | import StmtFSM :: *; 14 | import Wishbone :: *; 15 | 16 | interface KitchenSinkController#(numeric type wbAddr); 17 | interface Wishbone::Slave#(32, wbAddr, 4) slave; 18 | endinterface 19 | 20 | module mkKitchenSinkController(KitchenSinkController#(wbAddr)); 21 | Wishbone::SlaveConnector#(32, wbAddr, 4) bus <- mkSyncSlaveConnector; 22 | 23 | Reg#(Bit#(32)) upcounter <- mkReg(0); 24 | rule upcount; 25 | upcounter <= upcounter + 1; 26 | endrule 27 | 28 | rule wbRequest; 29 | let r <- bus.client.request.get(); 30 | let resp = SlaveResponse { readData: tagged Invalid }; 31 | $display("KSC: wb request", fshow(r)); 32 | 33 | case (r.address) matches 34 | 'h0: begin 35 | resp.readData = tagged Valid upcounter; 36 | end 37 | 'h4: begin 38 | // '105c' 39 | resp.readData = tagged Valid 32'h31303563; 40 | end 41 | 'h8: begin 42 | // 'q3k ' 43 | resp.readData = tagged Valid 32'h71336b20; 44 | end 45 | 'hc: begin 46 | // '2022' 47 | resp.readData = tagged Valid 32'h32303232; 48 | end 49 | endcase 50 | bus.client.response.put(resp); 51 | endrule 52 | 53 | interface slave = bus.slave; 54 | endmodule 55 | 56 | 57 | function Action doRead(Wishbone::MasterConnector#(32, 32, 4) master, Bit#(32) addr); 58 | return action 59 | master.server.request.put(SlaveRequest { address: addr 60 | , writeData: tagged Invalid 61 | , select: 4'b1111 62 | }); 63 | endaction; 64 | endfunction 65 | 66 | function Action doWrite(Wishbone::MasterConnector#(32, 32, 4) master, Bit#(32) addr, Bit#(32) data); 67 | return action 68 | master.server.request.put(SlaveRequest { address: addr 69 | , writeData: tagged Valid data 70 | , select: 4'b1111 71 | }); 72 | endaction; 73 | endfunction 74 | 75 | function Action eatResponse(Wishbone::MasterConnector#(32, 32, 4) master); 76 | action 77 | let _ <- master.server.response.get(); 78 | endaction 79 | endfunction 80 | 81 | function Action getResponse(Wishbone::MasterConnector#(32, 32, 4) master, Reg#(Bit#(32)) data); 82 | action 83 | let d <- master.server.response.get(); 84 | data <= fromMaybe(0, d.readData); 85 | endaction 86 | endfunction 87 | 88 | (* synthesize *) 89 | module mkTbKitchenSink(Empty); 90 | Wishbone::MasterConnector#(32, 32, 4) master <- mkMasterConnector; 91 | KitchenSinkController#(32) controller <- mkKitchenSinkController; 92 | mkConnection(master.master, controller.slave); 93 | 94 | Reg#(Bit#(32)) tmp1 <- mkReg(0); 95 | Reg#(Bit#(32)) tmp2 <- mkReg(0); 96 | Stmt test = seq 97 | doRead(master, 0); 98 | getResponse(master, tmp1); 99 | doRead(master, 0); 100 | getResponse(master, tmp2); 101 | dynamicAssert(tmp2 > tmp1, "counter should be counting"); 102 | 103 | doRead(master, 4); 104 | getResponse(master, tmp1); 105 | dynamicAssert(tmp1 == 32'h31303563, "magic value should be present"); 106 | endseq; 107 | mkAutoFSM(test); 108 | endmodule 109 | 110 | endpackage 111 | --------------------------------------------------------------------------------