TBM: The TBM executable, without the actual model The boilerplate stuff to get TBM started. The actual models will come in the following commits. This commit also includes a uArch configuration. Change-Id: I4d1054a46ee2c98aa277b3eeace76fb6ddf893ff
diff --git a/config/rvv-simple.yaml b/config/rvv-simple.yaml new file mode 100644 index 0000000..2ed2e8b --- /dev/null +++ b/config/rvv-simple.yaml
@@ -0,0 +1,491 @@ +{ + "description": "TBM configuration for Springbok", + + # This file is also used as an example of a TBM configuration, hence all the + # comments. + + # To experiment with different configurations based on this one, create a new + # configuration file with only the properties you want to change, and pass + # the new file to TBM with the '--extend' argument (this file should still be + # passed to TBM with the '--uarch' argument). You can use the '--extend' + # argument multiple times to pass multiple files. Normally, properties in + # 'extend' files overwrite (or add) properties. If you want to replace an + # entire object (i.e. not just overwrite the mentioned properties, also + # remove all the other properties), include the property 'replace : true' in + # the object. + + # config: object, required. General parameters of the microarchitecture. + "config" : { + # branch_prediction: enum, required. Possible values: + # 'none' - no branch prediction, fetch is stalled until the target is + # computed. + # 'perfect' - branching doesn't cause fetch stalls (the branch target is + # taken from the input trace). + "branch_prediction" : "perfect", + + # fetch_rate: positive integer, required. The number of instructions that + # are fetched in a cycle. If the fetch queue doesn't have enough space for + # all the instructions, none are fetched. + "fetch_rate" : 4, + + # fetch_queue_size: positive integer. The size of the fetch queue. If + # omitted, the queue is unrestricted (infinite). + "fetch_queue_size" : 4, + + # decode_rate: positive integer. The number of instructions that can be + # decoded and moved from the fetch queue to the dispatch queues in a cycle. + # If omitted, as many instructions as possible are decoded and moved. + "decode_rate" : 4, + + # vector_slices: positive integer, required. The number of slices each + # vector register is composed of. + "vector_slices" : 2 + }, + + # register_files: map, required. Currently the register file names X, F, V, + # and MISC are hard-coded in TBM. In the future, the 'regs' property will be + # used to determine which register file a register belongs to. All registers + # that are not X, F, or V, are MISC. + "register_files" : { + "X" : { + # type: enum, required. Possible values: + # 'scalar' - scalar registers. + # 'vector' - vector registers. + "type" : "scalar", + + # regs: array, currently ignored. + "regs" : [ + "x1", "x2", "x3", "x4", "x5", "x6", "x7", + "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", + "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", + "x24", "x25", "x26", "x27", "x28", "x29", "x30", "x31" + ], + + # read_ports: positive integer. The number of register reads that can be + # done in a cycle, excluding registers listed in the + # 'dedicated_read_ports' property. If omitted, any number of registers + # can be read in a cycle. + "read_ports" : 2, + + # write_ports: positive integer. Similar to 'read_ports'. + "write_ports" : 1 + }, + + "F" : { + "type" : "scalar", + "regs" : [ + "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", + "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", + "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23", + "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31" + ], + "read_ports" : 2, + "write_ports" : 1 + }, + + "V" : { + "type" : "vector", + "regs" : [ + "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", + "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15", + "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23", + "v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31" + ], + "read_ports" : 2, + "write_ports" : 1, + + # dedicated_read_ports: array. This property is only valid when the + # register file 'type' is 'vector'. See 'read_ports' for more + # information. + "dedicated_read_ports" : [ "v0" ] + + # dedicated_write_ports, array. Similar to 'dedicated_read_ports'. + }, + + "MISC" : { + "type" : "scalar" + } + }, + + # issue_queues: map, required. Currently the names S and V are hard-coded in + # TBM. + "issue_queues": { + "S" : { + # size: positive integer. The number of instructions the issue/dispatch + # queue can hold. If omitted, the queue is unrestricted (infinite). + "size" : 4 + }, + + "V" : { + "size" : 4 + } + }, + + # functional_units: map, required. + "functional_units" : { + "lsu" : { + # count: positive integer. The number of copies of this unit avilable. If + # omitted, one copy will be avilable. + "count" : 1, + + # type: enum, required. Possible values: + # 'scalar' - scalar datapath. + # 'vector' - vector datapath (see slices). + "type" : "scalar", + + # issue_queue: string, required. The issue queue that feeds this unit. + "issue_queue" : "S", + + # eiq_size: positive integer. If omitted, the queue is unrestricted + # (infinite). + "eiq_size" : 4, + + # can_skip_eiq: boolean, required. When true, instructions can move from + # the issue queue directly to the first pipeline stage. Otherwise, + # instructions must spend at list one cycle in the EIQ before they move + # to the first pipeline stage. + "can_skip_eiq" : true, + + # depth: positive integer, required. The number of stages in the + # pipeline. + "depth" : 3, + + # pipelined: boolean, required. When true, each pipeline stage can be + # populated with a different instruction. Otherwise, only one instruction + # can be in any stage of the pipeline. + "pipelined": true, + + # load_stage: non-negative integer. For units that read from memory, this + # is the pipeline stage (zero based) in which the memory accesses is + # initiated. + "load_stage": 1, + + # fixed_load_latency: non-negative integer, required when 'load_stage' is + # specified. A load instruction will stall the pipeline only if it + # reaches the pipeline stage 'load_stage + fixed_load_latency', and the + # memory value is not available yet. + "fixed_load_latency": 1, + + # store_stage: non-negative integer. Similar to 'load_stage'. + "store_stage": 1, + + # fixed_store_latency: non-negative integer, required when 'store_stage' + # is specified. Similar to 'fixed_load_latency'. + "fixed_store_latency": 1, + + # memory_interface: string, required when 'load_stage' or 'store_stage' + # are specified. This should be one of the cache levels (see + # 'memory_system'), or 'main'. For units that accesses memory, this is + # the memory module the unit interacts with. + "memory_interface" : "L1D", + + # writeback_buff_size: positive integer. The size of the register + # writeback buffer. If omitted, the buffer is unrestricted (infinite). + "writeback_buff_size": 2 + }, + + "alu" : { + "type" : "scalar", + "issue_queue" : "S", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 1, "pipelined": true, + "writeback_buff_size": 2 + }, + + "div" : { + "type" : "scalar", + "issue_queue" : "S", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 4, "pipelined": true, + "writeback_buff_size": 2 + }, + + "mul" : { + "type" : "scalar", + "issue_queue" : "S", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 1, "pipelined": true, + "writeback_buff_size": 2 + }, + + "fpu" : { + "type" : "scalar", + "issue_queue" : "S", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 1, "pipelined": true, + "writeback_buff_size": 2 + }, + + "branch" : { + "description" : "TODO(sflur): not sure how to handle these instruction, so added this unit for now.", + "type" : "scalar", + "issue_queue" : "S", + "eiq_size" : 1, "can_skip_eiq" : true, + "depth" : 1, "pipelined": false, + "writeback_buff_size": 1 + }, + + "csr" : { + "description" : "TODO(sflur): not sure how to handle these instructions, so added this unit for now.", + "type" : "scalar", + "issue_queue" : "S", + "eiq_size" : 1, "can_skip_eiq" : true, + "depth" : 1, "pipelined": false, + "writeback_buff_size": 2 + }, + + "vctrl" : { + "description" : "TODO(sflur): not sure how to handle these instructions, so added this unit for now.", + "type" : "scalar", + "issue_queue" : "S", + "eiq_size" : 1, "can_skip_eiq" : true, + "depth" : 1, "pipelined": false, + "writeback_buff_size": 1 + }, + + "vlsu" : { + "type" : "vector", + "issue_queue" : "V", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 3, "pipelined": true, + "load_stage": 1, "fixed_load_latency": 1, + "store_stage": 1, "fixed_store_latency": 1, + "memory_interface" : "L1D", + "writeback_buff_size": 2 + }, + + "valu" : { + "type" : "vector", + "issue_queue" : "V", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 2, "pipelined": true, + "writeback_buff_size": 2 + }, + + "vmac" : { + "type" : "vector", + "issue_queue" : "V", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 3, "pipelined": true, + "writeback_buff_size": 2 + }, + + "vdiv" : { + "type" : "vector", + "issue_queue" : "V", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 3, "pipelined": true, + "writeback_buff_size": 2 + }, + + "vperm" : { + "type" : "vector", + "issue_queue" : "V", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 3, "pipelined": true, + "writeback_buff_size": 2 + }, + + "vred" : { + "type" : "vector", + "issue_queue" : "V", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 2, "pipelined": true, + "writeback_buff_size": 2 + }, + + "vmsk" : { + "description" : "From the spec doc, this look more special, it is used in conjunction with other units.", + "type" : "vector", + "issue_queue" : "V", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 2, "pipelined": true, + "writeback_buff_size": 2 + }, + + "vmv" : { + "description" : "This unit is not in the spec doc, but I suspect it might be added later.", + "type" : "vector", + "issue_queue" : "V", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 1, "pipelined": true, + "writeback_buff_size": 2 + }, + + "vfspecial" : { + "description" : "Not in the spec doc! copied from old config", + "type" : "vector", + "issue_queue" : "V", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 2, "pipelined": true, + "writeback_buff_size": 2 + }, + + "vnimp" : { + "description" : "For instructions we don't care to implement", + "type" : "vector", + "issue_queue" : "V", + "eiq_size" : 4, "can_skip_eiq" : true, + "depth" : 1, "pipelined": false, + "writeback_buff_size": 2 + } + }, + + # pipe_maps: array, required. A list of files specifying the mapping from + # instructions to functional units. + "pipe_maps" : [ + "pipe_maps/riscv/missing.json", + "pipe_maps/riscv/custom.json", + "pipe_maps/riscv/rv32a.json", + "pipe_maps/riscv/rv32b.json", + "pipe_maps/riscv/rv32d.json", + "pipe_maps/riscv/rv32f.json", + "pipe_maps/riscv/rv32h.json", + "pipe_maps/riscv/rv32i.json", + "pipe_maps/riscv/rv32m.json", + "pipe_maps/riscv/rv32q.json", + "pipe_maps/riscv/rv32zfh.json", + "pipe_maps/riscv/rv64a.json", + "pipe_maps/riscv/rv64d.json", + "pipe_maps/riscv/rv64f.json", + "pipe_maps/riscv/rv64h.json", + "pipe_maps/riscv/rv64i.json", + "pipe_maps/riscv/rv64m.json", + "pipe_maps/riscv/rv64q.json", + "pipe_maps/riscv/rvp.json", + "pipe_maps/riscv/rvv.json", + "pipe_maps/riscv/rvv-pseudo.json", + "pipe_maps/riscv/springbok.json", + "pipe_maps/riscv/system.json" + ], + + "__comment__ additional pipe_maps" : [ + "pipe_maps/riscv/pseudo.json", + "pipe_maps/riscv/rv32c.json", + "pipe_maps/riscv/rv32d-zfh.json", + "pipe_maps/riscv/rv32k.json", + "pipe_maps/riscv/rv32q-zfh.json", + "pipe_maps/riscv/rv64b.json", + "pipe_maps/riscv/rv64c.json", + "pipe_maps/riscv/rv64k.json", + "pipe_maps/riscv/rv64zfh.json", + "pipe_maps/riscv/rvc.json", + "pipe_maps/riscv/rvk.json", + "pipe_maps/riscv/svinval.json" + ], + + # memory_system: object, required. Description of the memory hierarchy. + "memory_system" : { + # latencies: object, required. + "latencies" : { + # fetch_read: positive integer, required when levels is not empty. The + # number of cycles required for handling a fetch read request coming from + # higher levels, not including the handling of the request by lower + # levels. + "fetch_read" : 1, + + # fetch_write: positive integer, required with levels. + # Similar to fetch_read. + "fetch_write" : 1, + + # write: positive integer, required (TODO(sflur): maybe this shouldn't be + # required for instruction cache?). The number of cycles required for + # handling a write request coming from a functional unit, not including + # the handling of the request by lower levels. + "write" : 1 + + # read: positive integer, required when this level is used as a + # memory_interface. The number of cycles required for handling a read + # request coming from a functional unit, not including the handling of + # the request by lower levels. + }, + + # levels: map. Description of the lower cache levels. + "levels" : { + "L3" : { + # type: enum, required. Possible values: + # 'dcache' - data cache, used for serving load/store instructions. + # 'icache' - instruction cache, used for fetching instructions. + # 'unified' - used for both instructions and data. + "type" : "unified", + + # placement: object, required. Description of the placement policy. + "placement" : { + # type: enum, required. Possible values: + # 'direct_map' + # 'set_assoc' + "type" : "set_assoc", + + # set_size: positive integer, required when 'type' is 'set_assoc'. + "set_size" : 4, + + # replacement: enum, required when 'type' is 'set_assoc'. Possible values: + # 'LRU' + "replacement" : "LRU" + }, + + # write_policy: enum, required. Possible values: + # 'write_back' + # 'write_through' + "write_policy" : "write_back", + + # inclusion: enum, required with levels. Possible values: + # 'exclusive' + # 'inclusive' + "inclusion" : "exclusive", + + # line_size: positive integer, required. Cache line size in bits. + "line_size" : 128, + + # size: bytes, required. Can be a positive integer, or a string with + # one of the suffixes b, kb, mb, gb, tb, lowercase or uppercase (e.g. + # "4MB"). + "size" : "4MB", + + "latencies" : { + "fetch_read" : 1, + "fetch_write" : 1, + "write" : 1 + }, + "levels" : { + "L2" : { + "type" : "unified", + "placement" : { "type" : "set_assoc", + "set_size" : 4, + "replacement" : "LRU" + }, + "write_policy" : "write_back", + "inclusion" : "exclusive", + "line_size" : 128, + "size" : "256KB", + "latencies" : { "fetch_read" : 1, "fetch_write" : 1, "write" : 1 }, + "levels" : { + "L1D" : { + "type" : "dcache", + "placement" : { "type" : "set_assoc", + "set_size" : 4, + "replacement" : "LRU" + }, + "write_policy" : "write_back", + "line_size" : 128, + "size" : "32KB", + "latencies" : { "read" : 1, "write" : 1 } + }, + "L1I" : { + "type" : "icache", + "placement" : { "type" : "set_assoc", + "set_size" : 4, + "replacement" : "LRU" + }, + "write_policy" : "write_back", + "line_size" : 128, + "size" : "32KB", + "latencies" : { "read" : 1, "write" : 1 } + } + } + } + } + } + } + } +}
diff --git a/config/uarch.schema.json b/config/uarch.schema.json new file mode 100644 index 0000000..0148606 --- /dev/null +++ b/config/uarch.schema.json
@@ -0,0 +1,266 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id" : "uarch.schema.json", + + "title": "Microarchitecture Configuration", + "description": "TBM configuration for a microarchitecture", + + "$ref" : "#/$defs/object", + "properties" : { + "config" : { + "$ref" : "#/$defs/object", + "properties" : { + "branch_prediction" : { "enum" : ["none", "perfect"] }, + "fetch_rate" : { "$ref" : "#/$defs/positive_integer" }, + "fetch_queue_size" : { + "description" : "Infinite queue if omitted", + "$ref" : "#/$defs/positive_integer" + }, + "decode_rate" : { + "description" : "Unrestricted if omitted", + "$ref" : "#/$defs/positive_integer" + }, + "vector_slices" : { + "description" : "The number of slices each vector register is composed of", + "$ref" : "#/$defs/positive_integer" + } + }, + "unevaluatedProperties" : false, + "required" : ["branch_prediction", "fetch_rate", "vector_slices"] + }, + + "register_files" : { + "$ref" : "#/$defs/object", + "unevaluatedProperties" : { + "$ref" : "#/$defs/object", + "properties" : { + "type" : { "$ref" : "#/$defs/data_type" }, + "regs" : { + "$comment" : "TODO(sflur): TBM currently ignores `regs`", + "type" : "array", + "items" : { "type": "string" }, + "minItems" : 1, + "uniqueItems" : true + }, + "read_ports" : { "$ref" : "#/$defs/positive_integer" }, + "write_ports" : { "$ref" : "#/$defs/positive_integer" } + }, + "if" : { + "properties" : { "type" : { "const" : "vector" } } + }, "then" : { + "properties" : { + "dedicated_read_ports" : { + "description" : "Reading from the listed registers doesn't use a read port.", + "type" : "array", + "items" : { + "type" : "string", + "$comment" : "items should be from `regs`" + }, + "minItems" : 1, + "uniqueItems" : true + }, + "dedicated_write_ports" : { + "description" : "Writing to the listed registers doesn't use a write port.", + "type" : "array", + "items" : { + "type" : "string", + "$comment" : "items should be from `regs`" + }, + "minItems" : 1, + "uniqueItems" : true + } + } + }, + "unevaluatedProperties" : false, + "required" : ["type"] + } + }, + + "issue_queues" : { + "$ref" : "#/$defs/object", + "unevaluatedProperties" : { + "$ref" : "#/$defs/object", + "properties" : { + "size" : { "$ref" : "#/$defs/positive_integer" } + }, + "unevaluatedProperties" : false + }, + "minProperties": 1 + }, + + "functional_units" : { + "$ref" : "#/$defs/object", + "unevaluatedProperties" : { + "$ref" : "#/$defs/object", + "properties" : { + "count" : { "$ref" : "#/$defs/positive_integer" }, + "type" : { "$ref" : "#/$defs/data_type" }, + "issue_queue" : { + "type" : "string", + "$comment" : "should be one of the `issue_queues`" + }, + "eiq_size" : { + "description" : "Unrestricted if omitted", + "$ref" : "#/$defs/positive_integer" + }, + "can_skip_eiq" : { "type" : "boolean" }, + "depth" : { "$ref" : "#/$defs/positive_integer" }, + "pipelined": { "type" : "boolean" }, + "load_stage": { "$ref" : "#/$defs/non_negative_integer" }, + "store_stage": { "$ref" : "#/$defs/non_negative_integer" }, + "writeback_buff_size": { + "description" : "Unrestricted if omitted", + "$ref" : "#/$defs/positive_integer" + } + }, + "dependentSchemas" : { + "load_stage": { + "properties" : { + "fixed_load_latency": { "$ref" : "#/$defs/non_negative_integer" }, + "memory_interface" : { + "type" : "string", + "$comment" : "should be one of the cache levels, or `'main'`" + } + }, + "required" : ["fixed_load_latency", "memory_interface"] + }, + "store_stage": { + "properties" : { + "fixed_store_latency": { "$ref" : "#/$defs/non_negative_integer" }, + "memory_interface" : { + "type" : "string", + "$comment" : "should be one of the cache levels, or `'main'`" + } + }, + "required" : ["fixed_store_latency", "memory_interface"] + } + }, + "unevaluatedProperties" : false, + "required" : ["type", "issue_queue", "can_skip_eiq", "depth", + "pipelined"] + } + }, + + "pipe_maps" : { + "type" : "array", + "items" : { + "description" : "File (.json) path, that maps instructions to functional units", + "type": "string" + }, + "minItems" : 1, + "uniqueItems" : true + }, + + "memory_system" : { + "$ref" : "#/$defs/object", + "properties" : { + "latencies" : { "$ref" : "#/$defs/mem_latencies" }, + "levels" : { "$ref" : "#/$defs/cache_levels" } + }, + "unevaluatedProperties" : false, + "required" : ["latencies"] + } + }, + "unevaluatedProperties" : false, + "required" : ["config", "register_files", "issue_queues", "functional_units", + "pipe_maps", "memory_system"], + + "$defs" : { + "object" : { + "description" : "an object type that allows string `description` and `__comment__` prefix", + "type" : "object", + "properties" : { + "description" : { "type" : "string" } + }, + "patternProperties" : { "^__comment__" : true }, + "$comment" : "TBM also accepts the boolean 'replace' property in files passed with the '--extend' argument, but those are not validated separately, only the merged result is validated." + }, + + "positive_integer" : { + "type" : "integer", + "minimum" : 1 + }, + + "non_negative_integer" : { + "type" : "integer", + "minimum" : 0 + }, + + "data_type" : { "enum" : ["scalar", "vector"] }, + + "bits" : { + "type" : "integer" + }, + + "bytes" : { + "type" : ["string", "integer"], + "pattern" : "^\\d+(b|B|kb|KB|mb|MB|gb|GB|tb|TB)?$" + }, + + "mem_latencies" : { + "$ref" : "#/$defs/object", + "properties" : { + "fetch_read" : { "$ref" : "#/$defs/positive_integer" }, + "fetch_write" : { "$ref" : "#/$defs/positive_integer" }, + "read" : { "$ref" : "#/$defs/positive_integer" }, + "write" : { "$ref" : "#/$defs/positive_integer" } + }, + "unevaluatedProperties" : false + }, + + "cache_levels" : { + "$ref" : "#/$defs/object", + "unevaluatedProperties" : { + "$ref" : "#/$defs/object", + "properties" : { + "type" : { "enum" : ["unified", "dcache", "icache"] }, + "placement" : { + "$ref" : "#/$defs/object", + "properties" : { + "type" : { "enum" : ["direct_map", "set_assoc"] } + }, + "if" : { + "properties" : { "type" : { "constant" : "set_assoc" } } + }, "then" : { + "properties" : { + "set_size" : { + "description" : "the number of lines in the set; must be a power of 2", + "$ref" : "#/$defs/positive_integer" + }, + "replacement" : { "enum" : ["LRU"] } + }, + "required" : ["set_size", "replacement"] + }, + "unevaluatedProperties" : false, + "required" : ["type"] + }, + "write_policy" : { "enum" : ["write_back", "write_through"] }, + "line_size" : { + "description" : "the number of bits in a cache line; must be equal to `2^(3*n)`, for some `n`", + "$ref" : "#/$defs/bits", + "$ref" : "#/$defs/positive_integer" + }, + "size" : { + "description" : "the total capacity of the cache in bytes; must be a power of 2", + "$ref" : "#/$defs/bytes", + "minimum" : 1, + "pattern" : "^[1-9]" + }, + "latencies" : { "$ref" : "#/$defs/mem_latencies" }, + "levels" : { "$ref" : "#/$defs/cache_levels" } + }, + "dependentSchemas" : { + "levels" : { + "properties" : { + "inclusion" : { "enum" : ["exclusive", "inclusive"] } + }, + "required" : ["inclusion"] + } + }, + "unevaluatedProperties" : false, + "required" : ["type", "placement", "write_policy", "line_size", "size", + "latencies"] + } + } + } +}
diff --git a/tbm.py b/tbm.py new file mode 100755 index 0000000..956d21c --- /dev/null +++ b/tbm.py
@@ -0,0 +1,287 @@ +#! /usr/bin/env python3 + +"""Trace based model. + +Models the microarchitectural behavior of a processor +based on a trace obtained from a functional simulator. +""" + +import json +import logging +from typing import Any, Dict, Sequence +import sys + +import jsonschema +import yaml + +from cpu import CPU +from functional_trace import FunctionalTrace +from memory_system import MemorySystem +from scalar_pipe import ScalarPipe +import scoreboard +import tbm_options +import utilities +from vector_pipe import VectorPipe + + +logger = logging.getLogger("tbm") + + +def schema_validator() -> jsonschema.protocols.Validator: + # TODO(b/261619078): use importlib instead of relative path + schema_file_name = "config/uarch.schema.json" + + with open(schema_file_name, "r", encoding="ascii") as schema_file: + uarch_schema = json.load(schema_file) + + # Check that the schema is valid + vcls = jsonschema.validators.validator_for(uarch_schema) + try: + vcls.check_schema(uarch_schema) + except jsonschema.exceptions.SchemaError as e: + logger.error("in '%s':\n%s", schema_file_name, e.message) + sys.exit(1) + + return vcls(uarch_schema) + + +def validate_uarch(validator: jsonschema.protocols.Validator, + uarch: Dict[str, Any]): + errors = sorted(validator.iter_errors(uarch), key=lambda e: e.path) + if errors: + errs = [] + for err in errors: + if err.path: + errs.append("/".join(str(p) for p in err.path) + ": " + + err.message) + else: + errs.append(err.message) + logger.error("Found %d errors in the microarchitecture" + " configuration:\n%s", + len(errors), "\n".join(errs)) + sys.exit(1) + + +def load_config_file(name: str) -> Dict[str, Any]: + with open(name, "r", encoding="ascii") as file: + if name.endswith(".json"): + return json.load(file) + + if not name.endswith(".yaml"): + logger.warning("The file '%s' has an unrecognized suffix (expected" + " .json or .yaml). Trying to load it as YAML.", + name) + + return yaml.safe_load(file) + + +def load_uarch() -> Dict[str, Any]: + """Read micro-architecture description.""" + + # Read in the micro-architecture description. + uarch_desc = load_config_file(tbm_options.args.uarch) + + # Read in the micro-architecture description schema. + validator = schema_validator() + + # Check that the original (un-patched) uarch is valid. + validate_uarch(validator, uarch_desc) + + # Read additional modifications from files. + for fl in tbm_options.args.extend: + logger.info("Applying modifications from file '%s'", fl) + + data = load_config_file(fl) + + merge_config(uarch_desc, data) + + # Apply command line modifications. + for cl_set in tbm_options.args.set: + logger.info("Applying setting '%s'", cl_set) + + path, value = cl_set.split("=") + path = path.split(".") + + apply_setting(uarch_desc, path, json.loads(value)) + + # Check that the patched uarch is valid + if tbm_options.args.extend or tbm_options.args.set: + validate_uarch(validator, uarch_desc) + + remove_comments(uarch_desc) + + if not tbm_options.args.report_dont_include_cfg: + if tbm_options.args.report: + # Save to file + with open(tbm_options.args.report, "w", encoding="ascii") as out: + print("Configuration:", file=out) + print(json.dumps(uarch_desc, indent=2), file=out) + print(file=out) + else: + # Or print to stdout + print("Configuration:") + print(json.dumps(uarch_desc, indent=2)) + print() + + return uarch_desc + + +def apply_setting(uarch: Dict[str, Any], path: Sequence[str], + value: Any) -> None: + """Modify an element of micro-architectural description. + + Args: + uarch: uArch configuration to be modified + path: path through tree of dictionaries + value: value to be set + """ + # We expect path to be non-empty + assert path + + for idx, seg in enumerate(path): + if seg not in uarch: + logger.error("attempt to override non-existent element: %s", + ".".join(path[:idx+1])) + sys.exit(1) + + # Traverse down `path` to update uarch until the last element. + if idx < len(path) - 1: + uarch = uarch[seg] + else: + # Last element + logger.info("Changing '%s' to '%s'", seg, value) + uarch[seg] = value + + +def merge_config(uarch: Dict[str, Any], + modification: Dict[str, Any]) -> None: + """Merge modification tree into uarch description. + + Modification is performed by recursing down to leaves + replacing old entries with entries from modification. + + Args: + uarch: uArch configuration to be modified + modification: configuration to be merged into uarch + """ + + for key, val in modification.items(): + if (key in uarch and isinstance(val, dict) and not val.pop("replace", + False)): + merge_config(uarch[key], val) + else: + logger.info(" Replacing '%s' with '%s'", key, val) + uarch[key] = val + + +def remove_comments(desc: Dict[str, Any]) -> None: + comments = [ + k for k in desc if k == "description" or k.startswith("__comment__") + ] + + for k in comments: + del desc[k] + + for _, val in desc.items(): + if isinstance(val, dict): + remove_comments(val) + + +def create_scoreboard(uid: str, desc: Dict[str, Any], + config_desc: Dict[str, Any]): + if desc["type"] == "scalar": + return scoreboard.Preemptive(uid, desc) + + if desc["type"] == "vector": + return scoreboard.VecPreemptive(uid, desc, config_desc["vector_slices"]) + + assert False + + +def create_cpu(uarch_desc: Dict[str, Any], in_trace: FunctionalTrace) -> CPU: + """Create CPU.""" + + # Read in the pipe maps. + pipe_map = {} + pipe_map_keys = pipe_map.keys() # This is a dynamic view + for pm_file in uarch_desc["pipe_maps"]: + with open(pm_file, "r", encoding="ascii") as pm_io: + pm = json.load(pm_io) + + pm.pop("__comment__", None) + + if pipe_map_keys & pm.keys(): + logger.error("instruction(s) with multiple mappings: %s", + ", ".join(pipe_map.keys() & pm.keys())) + sys.exit(1) + + pipe_map.update(pm) + + pipe_map = {k: v for k, v in pipe_map.items() if v != "UNKNOWN"} + + rf_scoreboards = { + uid: create_scoreboard(uid, rf_desc, uarch_desc["config"]) + for uid, rf_desc in uarch_desc["register_files"].items() + } + + mem_sys = MemorySystem(uarch_desc["memory_system"]) + + cpu = CPU(pipe_map, rf_scoreboards, mem_sys, uarch_desc["config"], in_trace) + + for uid, iq_desc in uarch_desc["issue_queues"].items(): + cpu.sched_unit.add_queue(uid, iq_desc) + + for kind, fu_desc in uarch_desc["functional_units"].items(): + if fu_desc["type"] == "scalar": + cpu.exec_unit.add_pipe(kind, + [ScalarPipe(f"{kind}{i}", kind, fu_desc, cpu.mem_sys, + rf_scoreboards) + for i in range(fu_desc.get("count", 1))]) + else: + assert fu_desc["type"] == "vector" + + cpu.exec_unit.add_pipe(kind, + [VectorPipe(f"{kind}{i}", kind, fu_desc, + uarch_desc["config"]["vector_slices"], cpu.mem_sys, + rf_scoreboards) + for i in range(fu_desc.get("count", 1))]) + + return cpu + + +def main(argv: Sequence[str]) -> int: + tbm_options.parse_args(argv, description=__doc__) + # This assert convinces pytype that args is not None. + assert tbm_options.args is not None + + log_level = logging.WARNING + if tbm_options.args.verbose > 0: + log_level = logging.INFO + + utilities.logging_config(log_level) + + uarch = load_uarch() + + if tbm_options.args.trace is None: + tr = FunctionalTrace.from_json(sys.stdin, tbm_options.args.instructions) + cpu = create_cpu(uarch, tr) + cpu.simulate() + return 0 + + if tbm_options.args.json_trace: + with open(tbm_options.args.trace, "r", encoding="ascii") as in_trace: + tr = FunctionalTrace.from_json(in_trace, + tbm_options.args.instructions) + cpu = create_cpu(uarch, tr) + cpu.simulate() + return 0 + + with open(tbm_options.args.trace, "rb") as in_trace: + tr = FunctionalTrace.from_fb(in_trace, tbm_options.args.instructions) + cpu = create_cpu(uarch, tr) + cpu.simulate() + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:]))
diff --git a/tbm_options.py b/tbm_options.py new file mode 100644 index 0000000..159e8a3 --- /dev/null +++ b/tbm_options.py
@@ -0,0 +1,104 @@ +""" Store command-line options for global access. """ + +import argparse +from typing import Sequence + +args = None + + +def parse_args(argv: Sequence[str], description: str) -> None: + parser = argparse.ArgumentParser( + description=description, + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument("-u", + "--uarch", + required=True, + help="Microarchitecture configuration file", + metavar="JSON") + + parser.add_argument("-e", + "--extend", + action="append", + default=[], + help="Extension used to modify microarchitecture. This" + " option can be used multiple times.", + metavar="JSON") + + parser.add_argument("-s", + "--set", + action="append", + default=[], + help="Modify individual parts of the microarchitecture." + " This option can be used multiple times.", + metavar="PATH=VALUE") + + parser.add_argument("-r", + "--report", + help="Print report to FILE (otherwise report is printed" + " to stdout)", + metavar="FILE") + + parser.add_argument("--report-dont-include-cfg", + action='store_true', + help="Don't include the configuration with the report.", + dest="report_dont_include_cfg") + + parser.add_argument("--save-counters", + help="Save counters to FILE for later processing", + metavar="FILE", + dest="save_counters") + + parser.add_argument("-t", + "--print-trace", + choices=["detailed", "three-valued"], + help="Print cycle-by-cycle trace", + dest="print_trace") + + parser.add_argument("--print-from-cycle", + default=0, + type=int, + help="Start printing only from cycle N", + metavar="N", + dest="print_from_cycle") + + parser.add_argument("--cycles", + type=int, + help="Stop running after N cycles", + metavar="N", + dest="print_cycles") + + parser.add_argument("--instructions", + default="0:", + help="Restrict the run to the instructions between N" + " and M", + metavar="N:[M]") + + parser.add_argument("--json-trace", + action='store_true', + help="Read the input trace as JSON", + dest="json_trace") + + parser.add_argument("--json-trace-buffer-size", + type=int, + default=100000, + help="For efficiency, read N instructions at a time.", + metavar="N", + dest="json_trace_buffer_size") + + # The -v flag is setup so that verbose holds the number of times the flag + # was used. This is the standard way to use -v, even though at the moment + # we have only two levels of verbosity: warning (the default, with no -v), + # and info. + parser.add_argument("-v", + "--verbose", + default=0, + action="count", + help="Increase the verbosity level. By default only" + " errors and warnings will show. Use '-v' to also show" + " information messages.") + + parser.add_argument("trace", nargs="?", help="Input trace", metavar="FILE") + + global args # pylint: disable=global-statement + args = parser.parse_args(argv)