forked from libre-chip/cpu
		
	Compare commits
	
		
			3 commits
		
	
	
		
			80bfb4302f
			...
			c423dc4f15
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c423dc4f15 | |||
| 6b703b012a | |||
| 49d13648ec | 
					 7 changed files with 155 additions and 7 deletions
				
			
		
							
								
								
									
										9
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -210,6 +210,7 @@ name = "cpu"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "fayalite",
 | 
					 "fayalite",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -303,7 +304,7 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "fayalite"
 | 
					name = "fayalite"
 | 
				
			||||||
version = "0.3.0"
 | 
					version = "0.3.0"
 | 
				
			||||||
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#edcc5927a5f9ebca6df5720bb1f5931e50095a57"
 | 
					source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#0be9f9ce2329fd455a40f87e70c51a3ab672cc40"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "base64",
 | 
					 "base64",
 | 
				
			||||||
 "bitvec",
 | 
					 "bitvec",
 | 
				
			||||||
| 
						 | 
					@ -330,7 +331,7 @@ dependencies = [
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "fayalite-proc-macros"
 | 
					name = "fayalite-proc-macros"
 | 
				
			||||||
version = "0.3.0"
 | 
					version = "0.3.0"
 | 
				
			||||||
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#edcc5927a5f9ebca6df5720bb1f5931e50095a57"
 | 
					source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#0be9f9ce2329fd455a40f87e70c51a3ab672cc40"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "fayalite-proc-macros-impl",
 | 
					 "fayalite-proc-macros-impl",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
| 
						 | 
					@ -338,7 +339,7 @@ dependencies = [
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "fayalite-proc-macros-impl"
 | 
					name = "fayalite-proc-macros-impl"
 | 
				
			||||||
version = "0.3.0"
 | 
					version = "0.3.0"
 | 
				
			||||||
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#edcc5927a5f9ebca6df5720bb1f5931e50095a57"
 | 
					source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#0be9f9ce2329fd455a40f87e70c51a3ab672cc40"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "base16ct",
 | 
					 "base16ct",
 | 
				
			||||||
 "num-bigint",
 | 
					 "num-bigint",
 | 
				
			||||||
| 
						 | 
					@ -353,7 +354,7 @@ dependencies = [
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "fayalite-visit-gen"
 | 
					name = "fayalite-visit-gen"
 | 
				
			||||||
version = "0.3.0"
 | 
					version = "0.3.0"
 | 
				
			||||||
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#edcc5927a5f9ebca6df5720bb1f5931e50095a57"
 | 
					source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#0be9f9ce2329fd455a40f87e70c51a3ab672cc40"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "indexmap",
 | 
					 "indexmap",
 | 
				
			||||||
 "prettyplease",
 | 
					 "prettyplease",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@ rust-version = "1.89.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[workspace.dependencies]
 | 
					[workspace.dependencies]
 | 
				
			||||||
fayalite = { git = "https://git.libre-chip.org/libre-chip/fayalite.git", version = "0.3.0", branch = "master" }
 | 
					fayalite = { git = "https://git.libre-chip.org/libre-chip/fayalite.git", version = "0.3.0", branch = "master" }
 | 
				
			||||||
 | 
					serde = { version = "1.0.202", features = ["derive"] }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[profile.dev]
 | 
					[profile.dev]
 | 
				
			||||||
opt-level = 1
 | 
					opt-level = 1
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,3 +16,4 @@ version.workspace = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
fayalite.workspace = true
 | 
					fayalite.workspace = true
 | 
				
			||||||
 | 
					serde.workspace = true
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,9 +8,10 @@ use crate::{
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use fayalite::prelude::*;
 | 
					use fayalite::prelude::*;
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use std::num::NonZeroUsize;
 | 
					use std::num::NonZeroUsize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
 | 
					#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
 | 
				
			||||||
#[non_exhaustive]
 | 
					#[non_exhaustive]
 | 
				
			||||||
pub struct UnitConfig {
 | 
					pub struct UnitConfig {
 | 
				
			||||||
    pub kind: UnitKind,
 | 
					    pub kind: UnitKind,
 | 
				
			||||||
| 
						 | 
					@ -27,12 +28,14 @@ impl UnitConfig {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
 | 
					#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
 | 
				
			||||||
#[non_exhaustive]
 | 
					#[non_exhaustive]
 | 
				
			||||||
pub struct CpuConfig {
 | 
					pub struct CpuConfig {
 | 
				
			||||||
    pub units: Vec<UnitConfig>,
 | 
					    pub units: Vec<UnitConfig>,
 | 
				
			||||||
    pub out_reg_num_width: usize,
 | 
					    pub out_reg_num_width: usize,
 | 
				
			||||||
    pub fetch_width: NonZeroUsize,
 | 
					    pub fetch_width: NonZeroUsize,
 | 
				
			||||||
 | 
					    pub max_branches_per_fetch: NonZeroUsize,
 | 
				
			||||||
 | 
					    pub fetch_width_in_bytes: NonZeroUsize,
 | 
				
			||||||
    /// default value for [`UnitConfig::max_in_flight`]
 | 
					    /// default value for [`UnitConfig::max_in_flight`]
 | 
				
			||||||
    pub default_unit_max_in_flight: NonZeroUsize,
 | 
					    pub default_unit_max_in_flight: NonZeroUsize,
 | 
				
			||||||
    pub rob_size: NonZeroUsize,
 | 
					    pub rob_size: NonZeroUsize,
 | 
				
			||||||
| 
						 | 
					@ -46,6 +49,18 @@ impl CpuConfig {
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        v
 | 
					        v
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    pub const DEFAULT_MAX_BRANCHES_PER_FETCH: NonZeroUsize = {
 | 
				
			||||||
 | 
					        let Some(v) = NonZeroUsize::new(1) else {
 | 
				
			||||||
 | 
					            unreachable!();
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        v
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    pub const DEFAULT_FETCH_WIDTH_IN_BYTES: NonZeroUsize = {
 | 
				
			||||||
 | 
					        let Some(v) = NonZeroUsize::new(4) else {
 | 
				
			||||||
 | 
					            unreachable!();
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        v
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
    pub const DEFAULT_UNIT_MAX_IN_FLIGHT: NonZeroUsize = {
 | 
					    pub const DEFAULT_UNIT_MAX_IN_FLIGHT: NonZeroUsize = {
 | 
				
			||||||
        let Some(v) = NonZeroUsize::new(8) else {
 | 
					        let Some(v) = NonZeroUsize::new(8) else {
 | 
				
			||||||
            unreachable!();
 | 
					            unreachable!();
 | 
				
			||||||
| 
						 | 
					@ -57,6 +72,8 @@ impl CpuConfig {
 | 
				
			||||||
            units,
 | 
					            units,
 | 
				
			||||||
            out_reg_num_width: Self::DEFAULT_OUT_REG_NUM_WIDTH,
 | 
					            out_reg_num_width: Self::DEFAULT_OUT_REG_NUM_WIDTH,
 | 
				
			||||||
            fetch_width: Self::DEFAULT_FETCH_WIDTH,
 | 
					            fetch_width: Self::DEFAULT_FETCH_WIDTH,
 | 
				
			||||||
 | 
					            max_branches_per_fetch: Self::DEFAULT_MAX_BRANCHES_PER_FETCH,
 | 
				
			||||||
 | 
					            fetch_width_in_bytes: Self::DEFAULT_FETCH_WIDTH_IN_BYTES,
 | 
				
			||||||
            default_unit_max_in_flight: Self::DEFAULT_UNIT_MAX_IN_FLIGHT,
 | 
					            default_unit_max_in_flight: Self::DEFAULT_UNIT_MAX_IN_FLIGHT,
 | 
				
			||||||
            rob_size,
 | 
					            rob_size,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -117,3 +134,12 @@ impl CpuConfig {
 | 
				
			||||||
            [self.non_const_unit_nums().len()]
 | 
					            [self.non_const_unit_nums().len()]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl(get(|c| c.fetch_width.get()))]
 | 
				
			||||||
 | 
					pub type CpuConfigFetchWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl(get(|c| c.max_branches_per_fetch.get()))]
 | 
				
			||||||
 | 
					pub type CpuConfigMaxBranchesPerFetch<C: PhantomConstGet<CpuConfig>> = DynSize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl(get(|c| c.fetch_width_in_bytes.get()))]
 | 
				
			||||||
 | 
					pub type CpuConfigFetchWidthInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@
 | 
				
			||||||
// See Notices.txt for copyright information
 | 
					// See Notices.txt for copyright information
 | 
				
			||||||
pub mod config;
 | 
					pub mod config;
 | 
				
			||||||
pub mod instruction;
 | 
					pub mod instruction;
 | 
				
			||||||
 | 
					pub mod next_pc;
 | 
				
			||||||
pub mod reg_alloc;
 | 
					pub mod reg_alloc;
 | 
				
			||||||
pub mod register;
 | 
					pub mod register;
 | 
				
			||||||
pub mod unit;
 | 
					pub mod unit;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										117
									
								
								crates/cpu/src/next_pc.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								crates/cpu/src/next_pc.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,117 @@
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
				
			||||||
 | 
					// See Notices.txt for copyright information
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//! [Next-Instruction Logic](https://git.libre-chip.org/libre-chip/grant-tracking/issues/10)
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! The basic idea here is that there's a `next_pc` stage that sends predicted fetch PCs to the `fetch` stage,
 | 
				
			||||||
 | 
					//! the `fetch` stage's outputs eventually end up in the `decode` stage,
 | 
				
			||||||
 | 
					//! after the `decode` stage there's a `post_decode` stage (that may run in the same clock cycle as `decode`)
 | 
				
			||||||
 | 
					//! that checks that the fetched instructions' kinds match the predicted instruction kinds and that feeds
 | 
				
			||||||
 | 
					//! information back to the `fetch` stage to cancel fetches that need to be predicted differently.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{config::CpuConfig, util::array_vec::ArrayVec};
 | 
				
			||||||
 | 
					use fayalite::prelude::*;
 | 
				
			||||||
 | 
					use fayalite::util::ready_valid::ReadyValid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl]
 | 
				
			||||||
 | 
					pub enum PredictedCond {
 | 
				
			||||||
 | 
					    Taken,
 | 
				
			||||||
 | 
					    Fallthrough,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl]
 | 
				
			||||||
 | 
					pub struct PredictedFallthrough {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl]
 | 
				
			||||||
 | 
					pub enum BranchPredictionKind<CondKind> {
 | 
				
			||||||
 | 
					    Branch(HdlOption<CondKind>),
 | 
				
			||||||
 | 
					    IndirectBranch(HdlOption<CondKind>),
 | 
				
			||||||
 | 
					    Call(HdlOption<CondKind>),
 | 
				
			||||||
 | 
					    IndirectCall(HdlOption<CondKind>),
 | 
				
			||||||
 | 
					    Ret(HdlOption<CondKind>),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl(get(|c| c.max_branches_per_fetch.get() - 1))]
 | 
				
			||||||
 | 
					pub type NextPcPredictionMaxBranchesBeforeLast<C: PhantomConstGet<CpuConfig>> = DynSize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl(no_static)]
 | 
				
			||||||
 | 
					pub struct NextPcPrediction<C: PhantomConstGet<CpuConfig>> {
 | 
				
			||||||
 | 
					    pub fetch_pc: UInt<64>,
 | 
				
			||||||
 | 
					    pub async_interrupt: Bool,
 | 
				
			||||||
 | 
					    pub branches_before_last: ArrayVec<
 | 
				
			||||||
 | 
					        BranchPredictionKind<PredictedFallthrough>,
 | 
				
			||||||
 | 
					        NextPcPredictionMaxBranchesBeforeLast<C>,
 | 
				
			||||||
 | 
					    >,
 | 
				
			||||||
 | 
					    pub last_branch: HdlOption<BranchPredictionKind<PredictedCond>>,
 | 
				
			||||||
 | 
					    pub last_branch_target_pc: UInt<64>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl]
 | 
				
			||||||
 | 
					pub struct NextPcToFetchInterfaceInner {
 | 
				
			||||||
 | 
					    pub next_fetch_pc: UInt<64>,
 | 
				
			||||||
 | 
					    pub in_progress_fetches_to_cancel: UInt<8>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl(no_static)]
 | 
				
			||||||
 | 
					pub struct NextPcToFetchInterface<C: PhantomConstGet<CpuConfig>> {
 | 
				
			||||||
 | 
					    pub inner: ReadyValid<NextPcToFetchInterfaceInner>,
 | 
				
			||||||
 | 
					    pub config: C,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl(no_static)]
 | 
				
			||||||
 | 
					/// handles updating speculative branch predictor state (e.g. branch histories) when instructions retire,
 | 
				
			||||||
 | 
					/// as well as updating state when a branch instruction is mis-speculated.
 | 
				
			||||||
 | 
					pub struct NextPcToRetireInterface<C: PhantomConstGet<CpuConfig>> {
 | 
				
			||||||
 | 
					    // TODO: add needed fields
 | 
				
			||||||
 | 
					    pub config: C,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl(no_static)]
 | 
				
			||||||
 | 
					pub struct NextPcToPostDecodeInterface<C: PhantomConstGet<CpuConfig>> {
 | 
				
			||||||
 | 
					    // TODO: add needed fields
 | 
				
			||||||
 | 
					    pub config: C,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl(no_static)]
 | 
				
			||||||
 | 
					pub struct FetchToPostDecodeInterface<C: PhantomConstGet<CpuConfig>> {
 | 
				
			||||||
 | 
					    // TODO: add needed fields
 | 
				
			||||||
 | 
					    pub config: C,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl(no_static)]
 | 
				
			||||||
 | 
					pub struct PostDecodeOutputInterface<C: PhantomConstGet<CpuConfig>> {
 | 
				
			||||||
 | 
					    // TODO: add needed fields
 | 
				
			||||||
 | 
					    pub config: C,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl_module]
 | 
				
			||||||
 | 
					pub fn next_pc(config: PhantomConst<CpuConfig>) {
 | 
				
			||||||
 | 
					    #[hdl]
 | 
				
			||||||
 | 
					    let cd: ClockDomain = m.input();
 | 
				
			||||||
 | 
					    #[hdl]
 | 
				
			||||||
 | 
					    let to_fetch_interface: NextPcToFetchInterface<PhantomConst<CpuConfig>> =
 | 
				
			||||||
 | 
					        m.output(NextPcToFetchInterface[config]);
 | 
				
			||||||
 | 
					    #[hdl]
 | 
				
			||||||
 | 
					    let to_post_decode_interface: NextPcToPostDecodeInterface<PhantomConst<CpuConfig>> =
 | 
				
			||||||
 | 
					        m.output(NextPcToPostDecodeInterface[config]);
 | 
				
			||||||
 | 
					    #[hdl]
 | 
				
			||||||
 | 
					    let to_retire_interface: NextPcToRetireInterface<PhantomConst<CpuConfig>> =
 | 
				
			||||||
 | 
					        m.output(NextPcToRetireInterface[config]);
 | 
				
			||||||
 | 
					    todo!()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[hdl_module]
 | 
				
			||||||
 | 
					pub fn post_decode(config: PhantomConst<CpuConfig>) {
 | 
				
			||||||
 | 
					    #[hdl]
 | 
				
			||||||
 | 
					    let cd: ClockDomain = m.input();
 | 
				
			||||||
 | 
					    #[hdl]
 | 
				
			||||||
 | 
					    let from_next_pc_interface: NextPcToPostDecodeInterface<PhantomConst<CpuConfig>> =
 | 
				
			||||||
 | 
					        m.input(NextPcToPostDecodeInterface[config]);
 | 
				
			||||||
 | 
					    #[hdl]
 | 
				
			||||||
 | 
					    let from_fetch_interface: FetchToPostDecodeInterface<PhantomConst<CpuConfig>> =
 | 
				
			||||||
 | 
					        m.output(FetchToPostDecodeInterface[config]);
 | 
				
			||||||
 | 
					    #[hdl]
 | 
				
			||||||
 | 
					    let output: PostDecodeOutputInterface<PhantomConst<CpuConfig>> =
 | 
				
			||||||
 | 
					        m.output(PostDecodeOutputInterface[config]);
 | 
				
			||||||
 | 
					    todo!()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@ use fayalite::{
 | 
				
			||||||
    intern::{Intern, Interned},
 | 
					    intern::{Intern, Interned},
 | 
				
			||||||
    prelude::*,
 | 
					    prelude::*,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod alu_branch;
 | 
					pub mod alu_branch;
 | 
				
			||||||
pub mod unit_base;
 | 
					pub mod unit_base;
 | 
				
			||||||
| 
						 | 
					@ -36,7 +37,7 @@ macro_rules! all_units {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ) => {
 | 
					    ) => {
 | 
				
			||||||
        $(#[$enum_meta])*
 | 
					        $(#[$enum_meta])*
 | 
				
			||||||
        #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
 | 
					        #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
 | 
				
			||||||
        $vis enum $UnitKind {
 | 
					        $vis enum $UnitKind {
 | 
				
			||||||
            $(
 | 
					            $(
 | 
				
			||||||
                $(#[$variant_meta])*
 | 
					                $(#[$variant_meta])*
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue