Spaces:
Sleeping
Sleeping
Sai Kumar Taraka commited on
Commit ·
7ee417c
1
Parent(s): ea3135d
Address review: register metadata engine, coverage wiring, virtual seqr fix, Xcelium wrapper, generic RAL access
Browse files- src/api/server.py +1 -1
- src/config.py +1 -1
- src/generation/templates/register_info_pkg.sv.j2 +129 -0
- src/generation/templates/scoreboard.sv.j2 +7 -1
- src/generation/templates/virtual_sequencer.sv.j2 +17 -0
- src/main.py +1 -1
- src/models/template_model.py +14 -12
- src/pipeline.py +3 -0
- src/simulation/__init__.py +5 -1
- src/simulation/xcelium.py +188 -0
src/api/server.py
CHANGED
|
@@ -46,7 +46,7 @@ class HealthResponse(BaseModel):
|
|
| 46 |
status: str = "ok"
|
| 47 |
version: str = "0.3.0"
|
| 48 |
api_version: str = "v1"
|
| 49 |
-
simulators: List[str] = ["stub", "icarus"]
|
| 50 |
|
| 51 |
|
| 52 |
class VersionInfo(BaseModel):
|
|
|
|
| 46 |
status: str = "ok"
|
| 47 |
version: str = "0.3.0"
|
| 48 |
api_version: str = "v1"
|
| 49 |
+
simulators: List[str] = ["stub", "icarus", "vcs", "questa", "xcelium"]
|
| 50 |
|
| 51 |
|
| 52 |
class VersionInfo(BaseModel):
|
src/config.py
CHANGED
|
@@ -125,7 +125,7 @@ class AutoTrainConfig(BaseModel):
|
|
| 125 |
max_iterations: int = Field(default=5, ge=1, le=50)
|
| 126 |
coverage_target: float = Field(default=90.0, ge=0.0, le=100.0)
|
| 127 |
coverage_gain_min: float = Field(default=2.0, ge=0.0, description="Min % gain per iteration to continue")
|
| 128 |
-
simulator: str = Field(default="stub", pattern=r"^(stub|icarus|vcs|questa)$")
|
| 129 |
sim_timeout: int = Field(default=300, ge=10)
|
| 130 |
num_seeds: int = Field(default=3, ge=1, le=20, description="Number of regression seeds per iteration")
|
| 131 |
generate_regression_test: bool = True
|
|
|
|
| 125 |
max_iterations: int = Field(default=5, ge=1, le=50)
|
| 126 |
coverage_target: float = Field(default=90.0, ge=0.0, le=100.0)
|
| 127 |
coverage_gain_min: float = Field(default=2.0, ge=0.0, description="Min % gain per iteration to continue")
|
| 128 |
+
simulator: str = Field(default="stub", pattern=r"^(stub|icarus|vcs|questa|xcelium|xrun)$")
|
| 129 |
sim_timeout: int = Field(default=300, ge=10)
|
| 130 |
num_seeds: int = Field(default=3, ge=1, le=20, description="Number of regression seeds per iteration")
|
| 131 |
generate_regression_test: bool = True
|
src/generation/templates/register_info_pkg.sv.j2
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// UVM Register Metadata Package for {{ spec.design_name }}
|
| 2 |
+
// Auto-generated from YAML spec — drives metadata-driven sequences
|
| 3 |
+
// Enables generic register access without protocol-specific knowledge
|
| 4 |
+
{% set regs = spec.registers if spec.registers else [] %}
|
| 5 |
+
{% set addr_ints = regs|map(attribute='address')|map('replace', '0x', '')|map('replace', 'h', '')|map('int', 16)|list %}
|
| 6 |
+
{% set max_addr_val = addr_ints|max if addr_ints else 0 %}
|
| 7 |
+
{% set addr_bits = [max_addr_val.bit_length(), 3]|max %}
|
| 8 |
+
{% set sizes = regs|map(attribute='size')|select('number')|list %}
|
| 9 |
+
{% set data_width = sizes|max if sizes else 8 %}
|
| 10 |
+
|
| 11 |
+
package {{ spec.design_name }}_reg_info_pkg;
|
| 12 |
+
|
| 13 |
+
// -----------------------------------------------------------------------
|
| 14 |
+
// Access Policy Enum
|
| 15 |
+
// -----------------------------------------------------------------------
|
| 16 |
+
typedef enum bit [2:0] {
|
| 17 |
+
RO = 3'b001, // Read-only
|
| 18 |
+
WO = 3'b010, // Write-only
|
| 19 |
+
RW = 3'b011, // Read-Write
|
| 20 |
+
RC = 3'b100, // Read-Clear
|
| 21 |
+
W1C = 3'b101, // Write-1-to-Clear
|
| 22 |
+
NA = 3'b000 // Not applicable
|
| 23 |
+
} access_policy_e;
|
| 24 |
+
|
| 25 |
+
// -----------------------------------------------------------------------
|
| 26 |
+
// Field Information
|
| 27 |
+
// -----------------------------------------------------------------------
|
| 28 |
+
typedef struct {
|
| 29 |
+
string name;
|
| 30 |
+
int msb;
|
| 31 |
+
int lsb;
|
| 32 |
+
int width;
|
| 33 |
+
access_policy_e access;
|
| 34 |
+
logic [63:0] reset_val;
|
| 35 |
+
} field_info_t;
|
| 36 |
+
|
| 37 |
+
// -----------------------------------------------------------------------
|
| 38 |
+
// Register Information
|
| 39 |
+
// -----------------------------------------------------------------------
|
| 40 |
+
typedef struct {
|
| 41 |
+
string name;
|
| 42 |
+
logic [{{ addr_bits-1 }}:0] address;
|
| 43 |
+
int size;
|
| 44 |
+
access_policy_e access;
|
| 45 |
+
logic [63:0] reset_val;
|
| 46 |
+
int num_fields;
|
| 47 |
+
field_info_t fields[16];
|
| 48 |
+
} register_info_t;
|
| 49 |
+
|
| 50 |
+
// -----------------------------------------------------------------------
|
| 51 |
+
// Register Database
|
| 52 |
+
// -----------------------------------------------------------------------
|
| 53 |
+
function automatic register_info_t get_register_by_name(string name);
|
| 54 |
+
register_info_t r;
|
| 55 |
+
r.name = "";
|
| 56 |
+
{% for reg in regs %}
|
| 57 |
+
if (name == "{{ reg.name|lower }}") begin
|
| 58 |
+
r.name = "{{ reg.name }}";
|
| 59 |
+
r.address = {{ addr_bits }}'h{{ reg.address.replace('0x', '').replace('h', '') }};
|
| 60 |
+
r.size = {{ reg.size if reg.size else 8 }};
|
| 61 |
+
r.reset_val = {% if reg.reset is defined and reg.reset %}{{ reg.size if reg.size else 8 }}'h{{ reg.reset|string|replace('0x', '')|replace('h', '') }}{% else %}'h0{% endif %};
|
| 62 |
+
{% if reg.access is defined and reg.access %}
|
| 63 |
+
{% set ra = reg.access|lower %}
|
| 64 |
+
{% if ra == 'ro' %}r.access = RO;
|
| 65 |
+
{% elif ra == 'wo' %}r.access = WO;
|
| 66 |
+
{% elif ra == 'rw' %}r.access = RW;
|
| 67 |
+
{% elif ra == 'rc' %}r.access = RC;
|
| 68 |
+
{% elif ra == 'w1c' %}r.access = W1C;
|
| 69 |
+
{% else %}r.access = RW;
|
| 70 |
+
{% endif %}
|
| 71 |
+
{% else %}r.access = RW;
|
| 72 |
+
{% endif %}
|
| 73 |
+
{% if reg.fields %}
|
| 74 |
+
r.num_fields = {{ reg.fields|length }};
|
| 75 |
+
{% for f in reg.fields %}
|
| 76 |
+
{% if ':' in f.bits %}{% set parts = f.bits.split(':') %}{% set msb = parts[0]|int %}{% set lsb = parts[1]|int %}{% else %}{% set msb = f.bits|int - 1 %}{% set lsb = 0 %}{% endif %}
|
| 77 |
+
r.fields[{{ loop.index0 }}].name = "{{ f.name }}";
|
| 78 |
+
r.fields[{{ loop.index0 }}].msb = {{ msb }};
|
| 79 |
+
r.fields[{{ loop.index0 }}].lsb = {{ lsb }};
|
| 80 |
+
r.fields[{{ loop.index0 }}].width = {{ msb - lsb + 1 }};
|
| 81 |
+
r.fields[{{ loop.index0 }}].reset_val = {% if f.reset is defined and f.reset %}{{ msb - lsb + 1 }}'h{{ f.reset|string|replace('0x', '')|replace('h', '') }}{% else %}'h0{% endif %};
|
| 82 |
+
{% endfor %}
|
| 83 |
+
{% else %}
|
| 84 |
+
r.num_fields = 0;
|
| 85 |
+
{% endif %}
|
| 86 |
+
return r;
|
| 87 |
+
end
|
| 88 |
+
{% endfor %}
|
| 89 |
+
return r;
|
| 90 |
+
endfunction
|
| 91 |
+
|
| 92 |
+
function automatic register_info_t get_register_by_offset(logic [{{ addr_bits-1 }}:0] addr);
|
| 93 |
+
{% for reg in regs %}
|
| 94 |
+
if (addr == {{ addr_bits }}'h{{ reg.address.replace('0x', '').replace('h', '') }})
|
| 95 |
+
return get_register_by_name("{{ reg.name|lower }}");
|
| 96 |
+
{% endfor %}
|
| 97 |
+
register_info_t r;
|
| 98 |
+
r.name = "";
|
| 99 |
+
return r;
|
| 100 |
+
endfunction
|
| 101 |
+
|
| 102 |
+
// -----------------------------------------------------------------------
|
| 103 |
+
// Register Count
|
| 104 |
+
// -----------------------------------------------------------------------
|
| 105 |
+
function automatic int get_num_registers();
|
| 106 |
+
return {{ regs|length }};
|
| 107 |
+
endfunction
|
| 108 |
+
|
| 109 |
+
// -----------------------------------------------------------------------
|
| 110 |
+
// Register Name List (for iterating in sequences)
|
| 111 |
+
// -----------------------------------------------------------------------
|
| 112 |
+
function automatic void get_all_register_names(ref string names[$]);
|
| 113 |
+
{% for reg in regs %}
|
| 114 |
+
names.push_back("{{ reg.name|lower }}");
|
| 115 |
+
{% endfor %}
|
| 116 |
+
endfunction
|
| 117 |
+
|
| 118 |
+
// -----------------------------------------------------------------------
|
| 119 |
+
// Access Policy Helpers
|
| 120 |
+
// -----------------------------------------------------------------------
|
| 121 |
+
function automatic bit is_readable(access_policy_e ap);
|
| 122 |
+
return (ap inside {RO, RW, RC});
|
| 123 |
+
endfunction
|
| 124 |
+
|
| 125 |
+
function automatic bit is_writable(access_policy_e ap);
|
| 126 |
+
return (ap inside {WO, RW, W1C});
|
| 127 |
+
endfunction
|
| 128 |
+
|
| 129 |
+
endpackage
|
src/generation/templates/scoreboard.sv.j2
CHANGED
|
@@ -282,12 +282,14 @@ class {{ spec.design_name }}_scoreboard extends uvm_scoreboard;
|
|
| 282 |
end
|
| 283 |
{% endif %}
|
| 284 |
|
| 285 |
-
if (tr.addr inside {
|
| 286 |
if (tr.data === expected) begin
|
| 287 |
match_count++;
|
|
|
|
| 288 |
`uvm_info("SB", $sformatf("READ MATCH: addr=0x%0h data=0x%02h", tr.addr, tr.data), UVM_HIGH)
|
| 289 |
end else begin
|
| 290 |
mismatch_count++;
|
|
|
|
| 291 |
log_error($sformatf("READ MISMATCH: addr=0x%0h expected=0x%02h got=0x%02h",
|
| 292 |
tr.addr, expected, tr.data));
|
| 293 |
end
|
|
@@ -636,6 +638,10 @@ class {{ spec.design_name }}_scoreboard extends uvm_scoreboard;
|
|
| 636 |
end
|
| 637 |
cov_wordlen_seen[dbits - 5] = 1;
|
| 638 |
cov_parity_seen[pen + eps*2] = 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 639 |
`uvm_info("SB_REC_CFG", $sformatf("record_config: baud=%0d dbits=%0d sbits=%0d pen=%0d eps=%0d",
|
| 640 |
baud, dbits, sbits, pen, eps), UVM_MEDIUM)
|
| 641 |
endfunction
|
|
|
|
| 282 |
end
|
| 283 |
{% endif %}
|
| 284 |
|
| 285 |
+
if (tr.addr inside { {% for r in regs %}{{ addr_bits }}'h{{ r.address.replace('0x', '').replace('h', '') }}{% if not loop.last %}, {% endif %}{% endfor %} }) begin
|
| 286 |
if (tr.data === expected) begin
|
| 287 |
match_count++;
|
| 288 |
+
reg_match_count[tr.addr]++;
|
| 289 |
`uvm_info("SB", $sformatf("READ MATCH: addr=0x%0h data=0x%02h", tr.addr, tr.data), UVM_HIGH)
|
| 290 |
end else begin
|
| 291 |
mismatch_count++;
|
| 292 |
+
reg_mismatch_count[tr.addr]++;
|
| 293 |
log_error($sformatf("READ MISMATCH: addr=0x%0h expected=0x%02h got=0x%02h",
|
| 294 |
tr.addr, expected, tr.data));
|
| 295 |
end
|
|
|
|
| 638 |
end
|
| 639 |
cov_wordlen_seen[dbits - 5] = 1;
|
| 640 |
cov_parity_seen[pen + eps*2] = 1;
|
| 641 |
+
{% if p == "uart" %}
|
| 642 |
+
if (cov_handle != null)
|
| 643 |
+
cov_handle.sample_uart_config(baud, dbits, sbits, pen + eps*2);
|
| 644 |
+
{% endif %}
|
| 645 |
`uvm_info("SB_REC_CFG", $sformatf("record_config: baud=%0d dbits=%0d sbits=%0d pen=%0d eps=%0d",
|
| 646 |
baud, dbits, sbits, pen, eps), UVM_MEDIUM)
|
| 647 |
endfunction
|
src/generation/templates/virtual_sequencer.sv.j2
CHANGED
|
@@ -13,8 +13,17 @@ class {{ spec.design_name }}_virtual_sequencer extends uvm_sequencer;
|
|
| 13 |
|
| 14 |
{% if p == "uart" %}
|
| 15 |
// Additional UART sub-sequencers for multi-port / layered protocols
|
|
|
|
| 16 |
uvm_sequencer #({{ spec.design_name }}_seq_item) uart0_seqr;
|
|
|
|
| 17 |
uvm_sequencer #({{ spec.design_name }}_seq_item) uart1_seqr;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
{% elif p == "apb" %}
|
| 19 |
uvm_sequencer #({{ spec.design_name }}_seq_item) apb_seqr;
|
| 20 |
{% elif p == "axi4lite" %}
|
|
@@ -36,7 +45,15 @@ class {{ spec.design_name }}_virtual_sequencer extends uvm_sequencer;
|
|
| 36 |
{% if p == "uart" %}
|
| 37 |
void'(uvm_config_db #(uvm_sequencer #({{ spec.design_name }}_seq_item))::get(this, "", "bus_seqr", bus_seqr));
|
| 38 |
void'(uvm_config_db #(uvm_sequencer #({{ spec.design_name }}_seq_item))::get(this, "", "uart0_seqr", uart0_seqr));
|
|
|
|
| 39 |
void'(uvm_config_db #(uvm_sequencer #({{ spec.design_name }}_seq_item))::get(this, "", "uart1_seqr", uart1_seqr));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
{% else %}
|
| 41 |
void'(uvm_config_db #(uvm_sequencer #({{ spec.design_name }}_seq_item))::get(this, "", "bus_seqr", bus_seqr));
|
| 42 |
{% endif %}
|
|
|
|
| 13 |
|
| 14 |
{% if p == "uart" %}
|
| 15 |
// Additional UART sub-sequencers for multi-port / layered protocols
|
| 16 |
+
// Number of sub-seqrs = number of interfaces in spec
|
| 17 |
uvm_sequencer #({{ spec.design_name }}_seq_item) uart0_seqr;
|
| 18 |
+
{% if spec.interfaces|length > 1 %}
|
| 19 |
uvm_sequencer #({{ spec.design_name }}_seq_item) uart1_seqr;
|
| 20 |
+
{% endif %}
|
| 21 |
+
{% if spec.interfaces|length > 2 %}
|
| 22 |
+
uvm_sequencer #({{ spec.design_name }}_seq_item) uart2_seqr;
|
| 23 |
+
{% endif %}
|
| 24 |
+
{% if spec.interfaces|length > 3 %}
|
| 25 |
+
uvm_sequencer #({{ spec.design_name }}_seq_item) uart3_seqr;
|
| 26 |
+
{% endif %}
|
| 27 |
{% elif p == "apb" %}
|
| 28 |
uvm_sequencer #({{ spec.design_name }}_seq_item) apb_seqr;
|
| 29 |
{% elif p == "axi4lite" %}
|
|
|
|
| 45 |
{% if p == "uart" %}
|
| 46 |
void'(uvm_config_db #(uvm_sequencer #({{ spec.design_name }}_seq_item))::get(this, "", "bus_seqr", bus_seqr));
|
| 47 |
void'(uvm_config_db #(uvm_sequencer #({{ spec.design_name }}_seq_item))::get(this, "", "uart0_seqr", uart0_seqr));
|
| 48 |
+
{% if spec.interfaces|length > 1 %}
|
| 49 |
void'(uvm_config_db #(uvm_sequencer #({{ spec.design_name }}_seq_item))::get(this, "", "uart1_seqr", uart1_seqr));
|
| 50 |
+
{% endif %}
|
| 51 |
+
{% if spec.interfaces|length > 2 %}
|
| 52 |
+
void'(uvm_config_db #(uvm_sequencer #({{ spec.design_name }}_seq_item))::get(this, "", "uart2_seqr", uart2_seqr));
|
| 53 |
+
{% endif %}
|
| 54 |
+
{% if spec.interfaces|length > 3 %}
|
| 55 |
+
void'(uvm_config_db #(uvm_sequencer #({{ spec.design_name }}_seq_item))::get(this, "", "uart3_seqr", uart3_seqr));
|
| 56 |
+
{% endif %}
|
| 57 |
{% else %}
|
| 58 |
void'(uvm_config_db #(uvm_sequencer #({{ spec.design_name }}_seq_item))::get(this, "", "bus_seqr", bus_seqr));
|
| 59 |
{% endif %}
|
src/main.py
CHANGED
|
@@ -34,7 +34,7 @@ Examples:
|
|
| 34 |
parser.add_argument("--auto-train", action="store_true", help="Enable coverage-driven auto-training loop")
|
| 35 |
parser.add_argument("--max-iterations", type=int, default=5, help="Max auto-training iterations (default: 5)")
|
| 36 |
parser.add_argument("--coverage-target", type=float, default=90.0, help="Coverage target %% (default: 90)")
|
| 37 |
-
parser.add_argument("--simulator", default="stub", choices=["stub", "icarus", "vcs", "questa"],
|
| 38 |
help="Simulator backend (default: stub)")
|
| 39 |
return parser
|
| 40 |
|
|
|
|
| 34 |
parser.add_argument("--auto-train", action="store_true", help="Enable coverage-driven auto-training loop")
|
| 35 |
parser.add_argument("--max-iterations", type=int, default=5, help="Max auto-training iterations (default: 5)")
|
| 36 |
parser.add_argument("--coverage-target", type=float, default=90.0, help="Coverage target %% (default: 90)")
|
| 37 |
+
parser.add_argument("--simulator", default="stub", choices=["stub", "icarus", "vcs", "questa", "xcelium", "xrun"],
|
| 38 |
help="Simulator backend (default: stub)")
|
| 39 |
return parser
|
| 40 |
|
src/models/template_model.py
CHANGED
|
@@ -21,19 +21,21 @@ class TemplateModel(GenerationModel):
|
|
| 21 |
}
|
| 22 |
|
| 23 |
TEMPLATE_MAP = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
"testbench.sv": "testbench.sv.j2",
|
| 25 |
-
"
|
| 26 |
-
"sequence_item_{name}.sv": "sequence_item.sv.j2",
|
| 27 |
-
"driver_{name}.sv": "driver.sv.j2",
|
| 28 |
-
"monitor_{name}.sv": "monitor.sv.j2",
|
| 29 |
-
"serial_monitor_{name}.sv": "serial_monitor.sv.j2",
|
| 30 |
-
"agent_{name}.sv": "agent.sv.j2",
|
| 31 |
-
"scoreboard_{name}.sv": "scoreboard.sv.j2",
|
| 32 |
-
"coverage_collector_{name}.sv": "coverage_collector.sv.j2",
|
| 33 |
-
"ral_model_{name}.sv": "ral_model.sv.j2",
|
| 34 |
-
"base_sequence_{name}.sv": "sequence.sv.j2",
|
| 35 |
-
"test_{name}.sv": "test.sv.j2",
|
| 36 |
-
"environment_{name}.sv": "env.sv.j2",
|
| 37 |
}
|
| 38 |
|
| 39 |
RTL_MAP = {
|
|
|
|
| 21 |
}
|
| 22 |
|
| 23 |
TEMPLATE_MAP = {
|
| 24 |
+
"env/{name}_env.sv": "env.sv.j2",
|
| 25 |
+
"env/{name}_scoreboard.sv": "scoreboard.sv.j2",
|
| 26 |
+
"env/{name}_coverage_collector.sv": "coverage_collector.sv.j2",
|
| 27 |
+
"agent/{name}_driver.sv": "driver.sv.j2",
|
| 28 |
+
"agent/{name}_monitor.sv": "monitor.sv.j2",
|
| 29 |
+
"agent/{name}_sequencer.sv": "sequencer.sv.j2",
|
| 30 |
+
"agent/{name}_sequence_item.sv": "sequence_item.sv.j2",
|
| 31 |
+
"sequences/{name}_sequence.sv": "sequence.sv.j2",
|
| 32 |
+
"tests/{name}_test.sv": "test.sv.j2",
|
| 33 |
+
"sequences/{name}_reg_model_adapter.sv": "reg_model_adapter.sv.j2",
|
| 34 |
+
"sequences/{name}_register_info_pkg.sv": "register_info_pkg.sv.j2",
|
| 35 |
+
"{name}_reg_block.sv": "reg_block.sv.j2",
|
| 36 |
+
"{name}_interface.sv": "interface.sv.j2",
|
| 37 |
"testbench.sv": "testbench.sv.j2",
|
| 38 |
+
"rtl/protocol_core.v": "rtl/protocol_core.v.j2",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
}
|
| 40 |
|
| 41 |
RTL_MAP = {
|
src/pipeline.py
CHANGED
|
@@ -364,6 +364,9 @@ class TBPipeline:
|
|
| 364 |
if sim_type == "questa":
|
| 365 |
from src.simulation.questa import QuestaSimulator
|
| 366 |
return QuestaSimulator(work_dir=sim_output_path(self.cfg))
|
|
|
|
|
|
|
|
|
|
| 367 |
return StubSimulator(work_dir=sim_output_path(self.cfg))
|
| 368 |
|
| 369 |
def _merge_cfg(self, loaded: PipelineConfig) -> None:
|
|
|
|
| 364 |
if sim_type == "questa":
|
| 365 |
from src.simulation.questa import QuestaSimulator
|
| 366 |
return QuestaSimulator(work_dir=sim_output_path(self.cfg))
|
| 367 |
+
if sim_type == "xcelium" or sim_type == "xrun":
|
| 368 |
+
from src.simulation.xcelium import XceliumSimulator
|
| 369 |
+
return XceliumSimulator(work_dir=sim_output_path(self.cfg))
|
| 370 |
return StubSimulator(work_dir=sim_output_path(self.cfg))
|
| 371 |
|
| 372 |
def _merge_cfg(self, loaded: PipelineConfig) -> None:
|
src/simulation/__init__.py
CHANGED
|
@@ -1,6 +1,10 @@
|
|
| 1 |
from src.simulation.base import CoverageBin, CoverageDB, SimResult, Simulator
|
| 2 |
from src.simulation.icarus import IcarusSimulator
|
| 3 |
from src.simulation.stub_sim import StubSimulator
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
__all__ = ["Simulator", "SimResult", "CoverageBin", "CoverageDB",
|
| 6 |
-
"IcarusSimulator", "StubSimulator"
|
|
|
|
|
|
| 1 |
from src.simulation.base import CoverageBin, CoverageDB, SimResult, Simulator
|
| 2 |
from src.simulation.icarus import IcarusSimulator
|
| 3 |
from src.simulation.stub_sim import StubSimulator
|
| 4 |
+
from src.simulation.vcs import VcsSimulator
|
| 5 |
+
from src.simulation.questa import QuestaSimulator
|
| 6 |
+
from src.simulation.xcelium import XceliumSimulator
|
| 7 |
|
| 8 |
__all__ = ["Simulator", "SimResult", "CoverageBin", "CoverageDB",
|
| 9 |
+
"IcarusSimulator", "StubSimulator", "VcsSimulator",
|
| 10 |
+
"QuestaSimulator", "XceliumSimulator"]
|
src/simulation/xcelium.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import re
|
| 5 |
+
import subprocess
|
| 6 |
+
import tempfile
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import List, Optional
|
| 9 |
+
|
| 10 |
+
from src.simulation.base import CoverageBin, SimResult, Simulator
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class XceliumSimulator(Simulator):
|
| 14 |
+
"""Cadence Xcelium simulation backend.
|
| 15 |
+
|
| 16 |
+
Uses xrun for compile+elaborate+simulate in one step.
|
| 17 |
+
Detects xrun at runtime; falls back gracefully if not installed.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
def __init__(
|
| 21 |
+
self,
|
| 22 |
+
work_dir: str = "sim_output",
|
| 23 |
+
xrun_path: str = "xrun",
|
| 24 |
+
compile_flags: Optional[List[str]] = None,
|
| 25 |
+
run_flags: Optional[List[str]] = None,
|
| 26 |
+
):
|
| 27 |
+
super().__init__(work_dir)
|
| 28 |
+
self.xrun_path = xrun_path
|
| 29 |
+
self.compile_flags = compile_flags or [
|
| 30 |
+
"-64bit",
|
| 31 |
+
"-sv",
|
| 32 |
+
"-access",
|
| 33 |
+
"+rwc",
|
| 34 |
+
"-coverage",
|
| 35 |
+
"all",
|
| 36 |
+
"-nowarn",
|
| 37 |
+
"NONPORTTOPCONN",
|
| 38 |
+
]
|
| 39 |
+
self.run_flags = run_flags or [
|
| 40 |
+
"-coverage",
|
| 41 |
+
"all",
|
| 42 |
+
"-exit",
|
| 43 |
+
]
|
| 44 |
+
|
| 45 |
+
def run(
|
| 46 |
+
self,
|
| 47 |
+
files: List[str],
|
| 48 |
+
top: str = "testbench",
|
| 49 |
+
plusargs: Optional[List[str]] = None,
|
| 50 |
+
) -> SimResult:
|
| 51 |
+
work_dir = Path(self.work_dir)
|
| 52 |
+
work_dir.mkdir(parents=True, exist_ok=True)
|
| 53 |
+
|
| 54 |
+
# Check if xrun is available
|
| 55 |
+
try:
|
| 56 |
+
subprocess.run(
|
| 57 |
+
[self.xrun_path, "-version"],
|
| 58 |
+
capture_output=True,
|
| 59 |
+
timeout=10,
|
| 60 |
+
check=True,
|
| 61 |
+
)
|
| 62 |
+
except (FileNotFoundError, subprocess.TimeoutExpired, subprocess.CalledProcessError):
|
| 63 |
+
return SimResult(
|
| 64 |
+
passed=False,
|
| 65 |
+
errors=[f"Xcelium ({self.xrun_path}) not found or not executable"],
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# Write file list
|
| 69 |
+
flist_path = work_dir / "xrun_files.f"
|
| 70 |
+
flist_path.write_text("\n".join(str(f) for f in files), encoding="utf-8")
|
| 71 |
+
|
| 72 |
+
plusargs = plusargs or []
|
| 73 |
+
seed = None
|
| 74 |
+
for pa in plusargs:
|
| 75 |
+
if "+seed=" in pa:
|
| 76 |
+
seed = pa.split("=")[1]
|
| 77 |
+
break
|
| 78 |
+
|
| 79 |
+
xrun_cmd = [
|
| 80 |
+
self.xrun_path,
|
| 81 |
+
*self.compile_flags,
|
| 82 |
+
"-f",
|
| 83 |
+
str(flist_path),
|
| 84 |
+
"-top",
|
| 85 |
+
top,
|
| 86 |
+
"-l",
|
| 87 |
+
str(work_dir / "xrun_sim.log"),
|
| 88 |
+
]
|
| 89 |
+
if seed:
|
| 90 |
+
xrun_cmd += [f"+ntb_random_seed={seed}"]
|
| 91 |
+
xrun_cmd += self.run_flags
|
| 92 |
+
|
| 93 |
+
try:
|
| 94 |
+
result = subprocess.run(
|
| 95 |
+
xrun_cmd,
|
| 96 |
+
cwd=str(work_dir),
|
| 97 |
+
capture_output=True,
|
| 98 |
+
timeout=600,
|
| 99 |
+
)
|
| 100 |
+
log = result.stdout + "\n" + result.stderr
|
| 101 |
+
log_file = work_dir / "xrun_sim.log"
|
| 102 |
+
if log_file.exists():
|
| 103 |
+
log = log_file.read_text(encoding="utf-8", errors="replace")
|
| 104 |
+
|
| 105 |
+
if result.returncode != 0:
|
| 106 |
+
errors = self._parse_errors(log)
|
| 107 |
+
return SimResult(
|
| 108 |
+
passed=False,
|
| 109 |
+
errors=errors or ["Xcelium simulation failed"],
|
| 110 |
+
log_output=log[:5000],
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
return self.parse_coverage(log)
|
| 114 |
+
|
| 115 |
+
except subprocess.TimeoutExpired:
|
| 116 |
+
return SimResult(
|
| 117 |
+
passed=False,
|
| 118 |
+
errors=["Xcelium simulation timed out (>600s)"],
|
| 119 |
+
)
|
| 120 |
+
except Exception as e:
|
| 121 |
+
return SimResult(
|
| 122 |
+
passed=False,
|
| 123 |
+
errors=[f"Xcelium simulation error: {e}"],
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
def parse_coverage(self, log: str) -> SimResult:
|
| 127 |
+
bins: List[CoverageBin] = []
|
| 128 |
+
passed = True
|
| 129 |
+
errors: List[str] = []
|
| 130 |
+
|
| 131 |
+
xrun_cov = re.compile(
|
| 132 |
+
r"COVERAGE:\s+(\S+)\s+(\d+)/(\d+)\s+\[(HIT|MISS)\]"
|
| 133 |
+
)
|
| 134 |
+
for match in xrun_cov.finditer(log):
|
| 135 |
+
name = match.group(1)
|
| 136 |
+
hit = int(match.group(2))
|
| 137 |
+
goal = int(match.group(3))
|
| 138 |
+
bins.append(CoverageBin(name=name, hit_count=hit, goal=goal))
|
| 139 |
+
if hit < goal:
|
| 140 |
+
passed = False
|
| 141 |
+
|
| 142 |
+
# Xcelium coverage summary
|
| 143 |
+
total_cov = re.compile(
|
| 144 |
+
r"(Line|Toggle|Functional|Assertion)\s+Coverage:\s+([\d.]+)%"
|
| 145 |
+
)
|
| 146 |
+
for match in total_cov.finditer(log):
|
| 147 |
+
cov_type = match.group(1)
|
| 148 |
+
pct = float(match.group(2))
|
| 149 |
+
bins.append(CoverageBin(
|
| 150 |
+
name=f"xrun_{cov_type.lower()}_total",
|
| 151 |
+
hit_count=int(pct),
|
| 152 |
+
goal=100,
|
| 153 |
+
))
|
| 154 |
+
if pct < 100:
|
| 155 |
+
passed = False
|
| 156 |
+
|
| 157 |
+
# Error extraction
|
| 158 |
+
err_patterns = re.compile(
|
| 159 |
+
r"\*[WE]|(Error|Warning|Fatal):\s*(.*)", re.IGNORECASE
|
| 160 |
+
)
|
| 161 |
+
for match in err_patterns.finditer(log):
|
| 162 |
+
err_text = match.group(0).strip()
|
| 163 |
+
if len(err_text) > 20:
|
| 164 |
+
errors.append(err_text[:200])
|
| 165 |
+
|
| 166 |
+
if not bins:
|
| 167 |
+
# No structured coverage; check for pass/fail
|
| 168 |
+
if re.search(r"TEST.*PASSED|UVM.*PASSED", log, re.IGNORECASE):
|
| 169 |
+
passed = True
|
| 170 |
+
elif re.search(r"TEST.*FAILED|UVM.*FAILED|#\s*FAIL", log, re.IGNORECASE):
|
| 171 |
+
passed = False
|
| 172 |
+
errors.append("Simulation FAILED (no structured coverage found)")
|
| 173 |
+
|
| 174 |
+
return SimResult(
|
| 175 |
+
passed=passed,
|
| 176 |
+
total_bins=len(bins),
|
| 177 |
+
covered_bins=sum(1 for b in bins if b.covered),
|
| 178 |
+
bins=bins,
|
| 179 |
+
errors=errors[:20],
|
| 180 |
+
log_output=log[:5000],
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
def _parse_errors(self, log: str) -> List[str]:
|
| 184 |
+
errors = []
|
| 185 |
+
for line in log.split("\n"):
|
| 186 |
+
if any(kw in line for kw in ["*E", "*W", "Error:", "Fatal:", "FAIL"]):
|
| 187 |
+
errors.append(line.strip()[:200])
|
| 188 |
+
return errors[:20]
|