diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8678614a..d9ba7be9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,7 @@ jobs: cosmo_hp: ${{ steps.filter.outputs.cosmo_hp }} cosmo_seq: ${{ steps.filter.outputs.cosmo_seq }} cosmo_ignition: ${{ steps.filter.outputs.cosmo_ignition }} + cosmo_ignition_a: ${{ steps.filter.outputs.cosmo_ignition_a }} steps: - uses: actions/checkout@v4 with: @@ -113,6 +114,28 @@ jobs: path: "./buck-out/v2/gen/root/**/*" - name: Cleanup run: bash .github/cleanup.sh + cosmo_ignition_a: + needs: changes + if: ${{ needs.changes.outputs.buck2 == 'true' || needs.changes.outputs.cosmo_ignition == 'true' }} + runs-on: self-hosted + steps: + - run: echo "The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." + - name: Check out repository code + uses: actions/checkout@v4 + with: + submodules: 'true' + - name: Update pip reqs + run : python3 -m pip install --upgrade -r tools/requirements.txt --break-system-packages + - name: buck path + run: echo "~/.cargo/bin:/opt/Xilinx/Vivado/2024.1/bin:/opt/oss-cad-suite-20250211/bin" >> "$GITHUB_PATH" + - name: Build cosmo_ignition bitstream + run: buck2 build //hdl/projects/cosmo_ignition:cosmo_ignition_a --show-output + - uses: actions/upload-artifact@v4 + with: + name: cosmo-ignition-a-image + path: "./buck-out/v2/gen/root/**/*" + - name: Cleanup + run: bash .github/cleanup.sh bsv-streams: needs: changes diff --git a/hdl/ip/vhd/i2c/muxes/oximux16/oximux16_function.vhd b/hdl/ip/vhd/i2c/muxes/oximux16/oximux16_function.vhd index 4bc16ec9..fbe03072 100644 --- a/hdl/ip/vhd/i2c/muxes/oximux16/oximux16_function.vhd +++ b/hdl/ip/vhd/i2c/muxes/oximux16/oximux16_function.vhd @@ -59,21 +59,21 @@ architecture rtl of oximux16_function is reg0_pend : control0_type; data : std_logic_vector(7 downto 0); enable_allowed : std_logic) return boolean is - variable unrolled_reg : std_logic_vector(15 downto 0); - variable cnt_ones : integer range 0 to 16 := 0; + variable full_reg : unsigned(15 downto 0); begin -- allow only writes of 0 even when we're not allowed to enable if enable_allowed = '0' and (data /= 0 or std_logic_vector'(pack(reg0_pend)) /= 0) then return false; end if; - unrolled_reg := data & std_logic_vector'(pack(reg0_pend)); - for i in 0 to 15 loop - if unrolled_reg(i) = '1' then - cnt_ones := cnt_ones + 1; - end if; - end loop; - - if cnt_ones > 1 then + + -- Get the full 16 bit potential value + full_reg := unsigned(data) & unsigned'(pack(reg0_pend)); + -- Now check for 1-hotness. + -- Do this by doing x & (x - 1) == 0. Why does this work? + -- binary arith fun: x-1 flips all bits and doing a bitwise AND + -- will remove the lsb that was set. + + if (full_reg and (full_reg - 1)) /= 0 then return false; -- we only allow 1-hot writes to the control registers end if; if data(7) = '1' then diff --git a/hdl/projects/cosmo_ignition/BUCK b/hdl/projects/cosmo_ignition/BUCK index 893ac868..21d44c1f 100644 --- a/hdl/projects/cosmo_ignition/BUCK +++ b/hdl/projects/cosmo_ignition/BUCK @@ -4,7 +4,7 @@ load("//tools:yosys.bzl", "ice40_bitstream") vhdl_unit( name = "cosmo_ignition_top", - srcs = glob(["*.vhd"]), + srcs = glob(["*.vhd"], exclude=["cosmo_ignition_r1_top.vhd"]), deps = [ "//hdl/ip/vhd/ignition/target:ignition_target_common", "//hdl/ip/vhd/ignition/target:ignition_io", @@ -12,6 +12,24 @@ vhdl_unit( standard = "2008", ) +vhdl_unit( + name = "cosmo_ignition_a_top", + srcs = glob(["cosmo_ignition_a_top.vhd"]), + deps = [ + ":cosmo_ignition_top", + ], + standard = "2008", +) + +ice40_bitstream( + name="cosmo_ignition_a", + top_entity_name="cosmo_ignition_a_top", + top= ":cosmo_ignition_a_top", + family="hx8k", + package="bg121", + pinmap="cosmo_ignition.pcf" +) + ice40_bitstream( name="cosmo_ignition", top_entity_name="cosmo_ignition_top", diff --git a/hdl/projects/cosmo_ignition/cosmo_ignition_a_top.vhd b/hdl/projects/cosmo_ignition/cosmo_ignition_a_top.vhd new file mode 100644 index 00000000..e284df8a --- /dev/null +++ b/hdl/projects/cosmo_ignition/cosmo_ignition_a_top.vhd @@ -0,0 +1,84 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this +-- file, You can obtain one at https://mozilla.org/MPL/2.0/. +-- +-- Copyright 2025 Oxide Computer Company + +-- Cosmo Front Hot-plug FPGA targeting an ice40 HX8k + + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity cosmo_ignition_a_top is + port ( + clk_50mhz_ign_trgt_fpga : in std_logic; + ign_trgt_fpga_design_reset_l : in std_logic; + ign_trgt_fpga_debug_led : out std_logic_vector(3 downto 0); + ign_trgt_fpga_spare_v3p3 : out std_logic_vector(7 downto 0); + ign_trgt_id : in std_logic_vector(7 downto 0); + ign_trgt_fpga_lvds_status_led_en_l : out std_logic; + ign_trgt_fpga_pushbutton_reset_l : in std_logic; + lvds_rsw0_to_ign_trgt_fpga_p : inout std_logic; + lvds_ign_trgt_fpga_to_rsw0_p : inout std_logic; + lvds_ign_trgt_fpga_to_rsw0_n : inout std_logic; + lvds_rsw1_to_ign_trgt_fpga_p : inout std_logic; + lvds_ign_trgt_fpga_to_rsw1_p : inout std_logic; + lvds_ign_trgt_fpga_to_rsw1_n : inout std_logic; + v3p3_fpga2_a2_pg : in std_logic; + v1p2_fpga2_a2_pg : in std_logic; + v2p5_fpga2_a2_pg : in std_logic; + main_hsc_restart : out std_logic; + ibc_en : out std_logic; + v5p0_sys_a2_pg : in std_logic; + v3p3_sys_a2_pg : in std_logic; + v1p8_sys_a2_pg : in std_logic; + v1p0_mgmt_a2_pg : in std_logic; + v2p5_mgmt_a2_pg : in std_logic; + v12_sys_a2_pg_l : in std_logic; + main_hsc_pg : in std_logic; + sp_fault_l : in std_logic; + rot_fault_l : in std_logic + + ); +end entity; + +architecture rtl of cosmo_ignition_a_top is + +begin + + cosmo_ignition_top_inst: entity work.cosmo_ignition_top + generic map( + IS_HCV_A => true + ) + port map( + clk_50mhz_ign_trgt_fpga => clk_50mhz_ign_trgt_fpga, + ign_trgt_fpga_design_reset_l => ign_trgt_fpga_design_reset_l, + ign_trgt_fpga_debug_led => ign_trgt_fpga_debug_led, + ign_trgt_fpga_spare_v3p3 => ign_trgt_fpga_spare_v3p3, + ign_trgt_id => ign_trgt_id, + ign_trgt_fpga_lvds_status_led_en_l => ign_trgt_fpga_lvds_status_led_en_l, + ign_trgt_fpga_pushbutton_reset_l => ign_trgt_fpga_pushbutton_reset_l, + lvds_rsw0_to_ign_trgt_fpga_p => lvds_rsw0_to_ign_trgt_fpga_p, + lvds_ign_trgt_fpga_to_rsw0_p => lvds_ign_trgt_fpga_to_rsw0_p, + lvds_ign_trgt_fpga_to_rsw0_n => lvds_ign_trgt_fpga_to_rsw0_n, + lvds_rsw1_to_ign_trgt_fpga_p => lvds_rsw1_to_ign_trgt_fpga_p, + lvds_ign_trgt_fpga_to_rsw1_p => lvds_ign_trgt_fpga_to_rsw1_p, + lvds_ign_trgt_fpga_to_rsw1_n => lvds_ign_trgt_fpga_to_rsw1_n, + v3p3_fpga2_a2_pg => v3p3_fpga2_a2_pg, + v1p2_fpga2_a2_pg => v1p2_fpga2_a2_pg, + v2p5_fpga2_a2_pg => v2p5_fpga2_a2_pg, + main_hsc_restart => main_hsc_restart, + ibc_en => ibc_en, + v5p0_sys_a2_pg => v5p0_sys_a2_pg, + v3p3_sys_a2_pg => v3p3_sys_a2_pg, + v1p8_sys_a2_pg => v1p8_sys_a2_pg, + v1p0_mgmt_a2_pg => v1p0_mgmt_a2_pg, + v2p5_mgmt_a2_pg => v2p5_mgmt_a2_pg, + v12_sys_a2_pg_l => v12_sys_a2_pg_l, + main_hsc_pg => main_hsc_pg, + sp_fault_l => sp_fault_l, + rot_fault_l => rot_fault_l + ); +end rtl; \ No newline at end of file diff --git a/hdl/projects/cosmo_ignition/cosmo_ignition_top.vhd b/hdl/projects/cosmo_ignition/cosmo_ignition_top.vhd index 539cd3f2..1e6ae50e 100644 --- a/hdl/projects/cosmo_ignition/cosmo_ignition_top.vhd +++ b/hdl/projects/cosmo_ignition/cosmo_ignition_top.vhd @@ -15,6 +15,9 @@ use work.ignition_pkg.all; entity cosmo_ignition_top is + generic ( + IS_HCV_A : boolean := false + ); port ( clk_50mhz_ign_trgt_fpga : in std_logic; ign_trgt_fpga_design_reset_l : in std_logic; @@ -106,7 +109,11 @@ begin dbg => ign_trgt_fpga_spare_v3p3(4 downto 2) ); - main_hsc_restart <= not hotswap_restart_l; + hsc_gen: if IS_HCV_A generate + main_hsc_restart <= not hotswap_restart_l; + else generate + main_hsc_restart <= hotswap_restart_l; + end generate; ignition_io_inst: entity work.ignition_io port map( diff --git a/tools/fpga_releaser/config.toml b/tools/fpga_releaser/config.toml index 3d57071e..dac94568 100644 --- a/tools/fpga_releaser/config.toml +++ b/tools/fpga_releaser/config.toml @@ -8,6 +8,12 @@ toolchain = "yosys" job_name = "cosmo-ignition-image" builder = "buck2" toolchain = "yosys" +applicable_hcvs = ["b"] + +[cosmo-ignition-a] +job_name = "cosmo-ignition-a" +builder = "buck2" +toolchain = "yosys" [cosmo-seq] job_name = "cosmo-seq-image" diff --git a/tools/fpga_releaser/gh_releaser.py b/tools/fpga_releaser/gh_releaser.py index 02b12ac5..815d503e 100644 --- a/tools/fpga_releaser/gh_releaser.py +++ b/tools/fpga_releaser/gh_releaser.py @@ -11,7 +11,8 @@ def do_gh_release(api: GhApi, info, skip_mods=False): body = f"FPGA release for {info.name} made on {info.timestamp} UTC" # TODO: should we add something about timing failure here? # TODO: info about this being a locally generated file? - tag_name = name + applicable_hcvs = '_' + ''.join(info.applicable_hcvs) if info.applicable_hcvs else '' + tag_name = name + applicable_hcvs if info.gh_build_sha == "": # Built locally? Or not from a GH release sha = api.git.get_ref("heads/main")["object"].get("sha") diff --git a/tools/fpga_releaser/project.py b/tools/fpga_releaser/project.py index 5d874a80..f5247c37 100644 --- a/tools/fpga_releaser/project.py +++ b/tools/fpga_releaser/project.py @@ -9,7 +9,7 @@ from fpga_releaser.archive_parser import get_relevant_files_from_buck_zip from fpga_releaser import config_toml -def filter_wtih_exclusions(path, exclusions): +def filter_with_exclusions(path, exclusions): for e in exclusions: if e in path: return False @@ -23,12 +23,13 @@ def __init__(self, name, job_name, hubris_path, toolchain, builder): self.toolchain = toolchain self.builder = builder self.archive = None - self.filenames = [] + self.filenames = None self._timestamp = datetime.datetime.now() self.gh_release_name = "" self.gh_release_url = "" self.gh_build_sha = "" self.local = False + self.applicable_hcvs = None @classmethod def from_dict(cls, fpga_name, data: dict): @@ -49,7 +50,7 @@ def _filter_names(self, exclusions=None): exclusions = [] else: exclusions = set(exclusions) - filtered_filenames = [x for x in self.filenames if filter_wtih_exclusions(x, exclusions)] + filtered_filenames = [x for x in self.filenames if filter_with_exclusions(x, exclusions)] return filtered_filenames def materialize_relevant_files(self, export_path, exclusions=None):