util/spd_tools: Remove old lp4x and ddr4 versions of spd_tools
The migration to the new unified version of spd_tools is complete, so the old lp4x and ddr4 versions can be removed. BUG=b:191776301 TEST=None Signed-off-by: Reka Norman <rekanorman@google.com> Change-Id: I6b1fc297739efc8dc7d7eec64956bf3343984604 Reviewed-on: https://review.coreboot.org/c/coreboot/+/57822 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Furquan Shaikh <furquan@google.com> Reviewed-by: Tim Wawrzynczak <twawrzynczak@chromium.org>
This commit is contained in:
		
				
					committed by
					
						 Furquan Shaikh
						Furquan Shaikh
					
				
			
			
				
	
			
			
			
						parent
						
							8f690dd762
						
					
				
				
					commit
					e4cf38ed36
				
			| @@ -1,294 +0,0 @@ | ||||
| # DDR4 SPD tools README | ||||
|  | ||||
| Tools for generating SPD files for DDR4 memory used in platforms with | ||||
| memory down configuration. These tools generate SPDs following JESD79-4C | ||||
| and Jedec 4.1.2.L-5 R29 v103 specifications. | ||||
|  | ||||
| There are two tools provided that assist with generating SPDs and Makefiles | ||||
| to integrate in coreboot build. These tools can also be used to allocate | ||||
| DRAM IDs (configure DRAM hardware straps) for any DDR4 memory part used | ||||
| by the board. | ||||
|  | ||||
| * gen_spd.go: Generates de-duplicated SPD files using a global memory | ||||
|   part list provided by the mainboard in JSON format. Additionally, | ||||
|   generates a SPD manifest file(in CSV format) with information about | ||||
|   what memory part from the global list uses which of the generated | ||||
|   SPD files. | ||||
|  | ||||
| * gen_part_id.go: Allocates DRAM strap IDs for different DDR4 | ||||
|   memory parts used by the board. Takes as input a list of memory parts | ||||
|   used (in CSV format) by the board with optional fixed ids and the SPD | ||||
|   manifest file generated by gen_spd.go. Generates Makefile.inc for | ||||
|   integrating the generated SPD files in the coreboot build. | ||||
|  | ||||
| ## Tool 1 - gen_spd.go | ||||
|  | ||||
| This program takes as input: | ||||
| * Pointer to directory where the generated SPD files and manifest will | ||||
|   be placed. | ||||
| * JSON file containing a global list of memory parts with their | ||||
|   attributes as per the datasheet. This is the list of all known | ||||
|   DDR4 memory parts irrespective of their usage on the board. | ||||
| * SoC platform name for which the SPDs are being generated. Currently | ||||
|   supported platform names are `TGL`, `PCO` and `PLK`. | ||||
|  | ||||
| Input JSON file requires the following two fields for every memory part: | ||||
| * `name`: Name of the memory part | ||||
| * `attribs`: List of attributes of the memory part as per its | ||||
|   datasheet. These attributes match the part specifications and are | ||||
|   independent of any SoC expectations. Tool takes care of translating | ||||
|   the physical attributes of the memory part to match JEDEC and Intel | ||||
|   MRC expectations. | ||||
|  | ||||
| `attribs` field further contains two types of sub-fields: | ||||
| * Mandatory: These attributes have to be provided for a memory part. | ||||
| * Optional: These attributes can be provided by memory part if it wants | ||||
|   to override the defaults. | ||||
|  | ||||
| ### Mandatory `attribs` | ||||
|  | ||||
| * `speedMTps`: Maximum rate supported by the part in MT/s. Valid values: | ||||
|   `1600, 1866, 2133, 2400, 2666, 2933, 3200` MT/s. | ||||
|  | ||||
| * `CL_nRCD_nRP`: Refers to CAS Latency specified for the part (find | ||||
|   "CL-nRCD-nRP" in the vendor spec for the DDR4 part). | ||||
|  | ||||
| * `capacityPerDieGb`: Capacity per die in gigabits.  Valid values: | ||||
|   `2, 4, 8, 16` Gb part. | ||||
|  | ||||
| * `diesPerPackage`: Number of dies on the part.  Valid values: | ||||
|   `1, 2` dies per package. | ||||
|  | ||||
| * `packageBusWidth`: Number of bits of the device's address bus.  Valid values: | ||||
|   `8, 16` bit-wide bus. NOTE: Width of x4 is not supported by this tool. | ||||
|  | ||||
| * `ranksPerPackage`: From Jedec doc 4_01_02_AnnexL-1R23: | ||||
|   “Package ranks per DIMM” refers to the collections of devices on the module | ||||
|   sharing common chip select signals (across the data width of the DIMM), | ||||
|   either from the edge connector for unbuffered modules or from the outputs of | ||||
|   a registering clock driver for RDIMMs and LRDIMMs.Number of bits of the | ||||
|   device's address bus.  Valid values: | ||||
|   `1, 2` package ranks. | ||||
|  | ||||
| ### Optional `attribs` | ||||
|  | ||||
| The following options are calculated by the tool based on the mandatory | ||||
| attributes described for the part, but there may be cases where a default value | ||||
| must be overridden, such as when a device appears to be 3200AA, but does not | ||||
| support all of the CAS latencies typically supported by a speed bin 3200AA part. | ||||
| Do deal with such a case, the variable can be overridden here and the tool will | ||||
| use this value instead of calculating one.  All values must be defined in | ||||
| picosecond units, except for "CASLatencies", which would be represented as a | ||||
| string like "9 10 11 12 14". | ||||
|  | ||||
|  * `TAAMinPs`: Defines the minimum CAS Latency. | ||||
|    Table 48 of Jedec doc 4_01_02_AnnexL-5R29 lists tAAmin for each speed grade. | ||||
|  | ||||
|  * `TRASMinPs`: Refers to the minimum active to precharge delay time. | ||||
|    Table 55 of Jedec doc 4_01_02_AnnexL-5R29 lists tRPmin for each speed grade. | ||||
|  | ||||
|  * `TCKMinPs`: Refers to the minimum clock cycle time. | ||||
|    Table 42 of Jedec doc 4_01_02_AnnexL-5R29 lists tCKmin for each speed grade. | ||||
|  | ||||
|  * `TCKMaxPs`:Refers to the minimum clock cycle time. | ||||
|    Table 44 of Jedec doc 4_01_02_AnnexL-5R29 lists tCKmin for each speed grade. | ||||
|  | ||||
|  * `TRFC1MinPs`: Refers to the minimum refresh recovery delay time. | ||||
|    Table 59 of Jedec doc 4_01_02_AnnexL-5R29 lists tRFC1min for each page size. | ||||
|  | ||||
|  * `TRFC2MinPs`: Refers to the minimum refresh recovery delay time. | ||||
|    Table 61 of Jedec doc 4_01_02_AnnexL-5R29 lists tRFC2min for each page size. | ||||
|  | ||||
|  * `TRFC4MinPs`: Refers to the minimum refresh recovery delay time. | ||||
|    Table 63 of Jedec doc 4_01_02_AnnexL-5R29 lists tRFC4min for each page size. | ||||
|  | ||||
|  * `TFAWMinPs`:: Refers to the minimum four activate window delay time. | ||||
|    Table 66 of Jedec doc 4_01_02_AnnexL-5R29 lists tFAWmin for each speed grade | ||||
|    and page size combination. | ||||
|  | ||||
|  * `TRRDSMinPs`: Refers to the minimum activate to activate delay time to | ||||
|    different bank groups. | ||||
|    Table 68 of Jedec doc 4_01_02_AnnexL-5R29 lists tRRD_Smin for each speed grade | ||||
|    and page size combination. | ||||
|  | ||||
|  * `TRRDLMinPs`: Refers to the minimum activate to activate delay time to the | ||||
|    same bank group. | ||||
|    Table 70 of Jedec doc 4_01_02_AnnexL-5R29 lists tRRD_Lmin for each speed grade | ||||
|    and page size combination. | ||||
|  | ||||
|  * `TCCDLMinPs`: Refers to the minimum CAS to CAS delay time to same bank group. | ||||
|    Table 72 of Jedec doc 4_01_02_AnnexL-5R29 lists tCCD_Lmin for each speed grade. | ||||
|  | ||||
|  * `TWRMinPs`: Refers to the minimum write recovery time. | ||||
|    Table 75 of Jedec doc 4_01_02_AnnexL-5R29 lists tWRmin for each ddr4 type. | ||||
|  | ||||
|  * `TWTRSMinPs`: Refers to minimum write to read time to different bank group. | ||||
|    Table 78 of Jedec doc 4_01_02_AnnexL-5R29 lists tWTR_Smin for each ddr4 type. | ||||
|  | ||||
|  * `TWTRLMinPs`: Refers to minimum write to read time to same bank group. | ||||
|    Table 80 of Jedec doc 4_01_02_AnnexL-5R29 lists tWTR_Lmin for each ddr4 type. | ||||
|  | ||||
|  * `CASLatencies`: Refers to the CAS latencies supported by the part. | ||||
|    The speed bin tables in the back of Jedec doc 4_01_02_AnnexL-5R29 define the | ||||
|    standard CAS latencies that a speed bin part is supposed to support. | ||||
|    In cases where a part does not support all of the CAS latencies listed in the | ||||
|    speed bin tables, this entry should be used to override the default settings. | ||||
|  | ||||
| ### Example JSON file | ||||
| ``` | ||||
| { | ||||
|     "parts": [ | ||||
|         { | ||||
|             "name": "MEMORY_PART_A", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22 | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1, | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MEMORY_PART_B", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22 | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 2, | ||||
|                 "casLatencies": "9 10 11 12 13 14 15 16 17 18 19 20", | ||||
|                 "tCKMaxPs": "1250" | ||||
|             } | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Output | ||||
|  | ||||
| This tool generates the following files using the global list of | ||||
| memory parts in JSON format as described above: | ||||
|   * De-duplicated SPDs required for the different memory parts. These | ||||
|     SPD files are named (ddr4-spd-1.hex, ddr4-spd-2.hex, and so on) | ||||
|     and placed in the directory provided as an input to the tool. | ||||
|   * CSV file representing which of the deduplicated SPD files is used | ||||
|     by which memory part. This file is named as | ||||
|     `ddr4_spd_manifest.generated.txt` and placed in the directory provided | ||||
|     as an input to the tool along with the generated SPD | ||||
|     files. Example CSV file: | ||||
|     ``` | ||||
|     MEMORY_PART_A, ddr4-spd-1.hex | ||||
|     MEMORY_PART_B, ddr4-spd-2.hex | ||||
|     MEMORY_PART_C, ddr4-spd-3.hex | ||||
|     MEMORY_PART_D, ddr4-spd-2.hex | ||||
|     MEMORY_PART_E, ddr4-spd-2.hex | ||||
|     ``` | ||||
|  | ||||
| ## Tool 2 - gen_part_id.go | ||||
|  | ||||
| This program takes as input: | ||||
| * Pointer to directory where the SPD files and the manifest file | ||||
|   `ddr4_spd_manifest.generated.txt` (in CSV format) are placed by | ||||
|   gen_spd.go | ||||
| * CSV file containing list of memory parts used by the board and optional | ||||
|   fixed id. Each line of the file is supposed to contain one memory part `name` | ||||
|   as present in the global list of memory parts provided to gen_spd.go. | ||||
|   Optionally a fixed id may also be assigned to the part if required. | ||||
|   NOTE: Only assign a fixed ID if required for legacy reasons. | ||||
|  | ||||
| * Pointer to directory where the generated Makefile.inc should be | ||||
|   placed by the tool. | ||||
|  | ||||
| Sample input (mem_parts_used_file.txt): | ||||
| ``` | ||||
| K4AAG165WA-BCWE,1 | ||||
| MT40A512M16TB-062E:J | ||||
| MT40A1G16KD-062E:E | ||||
| K4A8G165WC-BCWE | ||||
| H5AN8G6NDJR-XNC,8 | ||||
| H5ANAG6NCMR-XNC | ||||
| ``` | ||||
| NOTE: This will ensure SPDs compatible with K4AAG165WA-BCWE and H5AN8G6NDJR-XNC | ||||
| are assigned to ID 1 and 8 respectively. All other memory parts will be | ||||
| assigned to the first compatible ID. Assigning fixed IDs may result in | ||||
| duplicate SPD entries or gaps in the ID mapping. | ||||
|  | ||||
| ### Output | ||||
|  | ||||
| This program provides the following: | ||||
|  | ||||
| * Prints out the list of DRAM hardware strap IDs that should be | ||||
|   allocated to each memory part listed in the input file. | ||||
| * Makefile.inc is generated in the provided directory to integrate | ||||
|   SPDs generated by gen_spd.go with the coreboot build for the board. | ||||
| * dram_id.generated.txt is generated in the same directory as | ||||
|   Makefile. This contains the part IDs assigned to the different | ||||
|   memory parts. (Useful to integrate in board schematics). | ||||
|  | ||||
| Sample output (dram_id.generated.txt): | ||||
| ``` | ||||
| DRAM Part Name                 ID to assign | ||||
| MEMORY_PART_A                  0 (0000) | ||||
| MEMORY_PART_B                  1 (0001) | ||||
| MEMORY_PART_C                  2 (0010) | ||||
| MEMORY_PART_D                  1 (0001) | ||||
| ``` | ||||
|  | ||||
| Sample Makefile.inc: | ||||
| ``` | ||||
| ## SPDX-License-Identifier: GPL-2.0-or-later | ||||
| ## This is an auto-generated file. Do not edit!! | ||||
|  | ||||
| SPD_SOURCES = | ||||
| SPD_SOURCES += ddr4-spd-1.hex      # ID = 0(0b0000)  Parts = MEMORY_PART_A | ||||
| SPD_SOURCES += ddr4-spd-2.hex      # ID = 1(0b0001)  Parts = MEMORY_PART_B, MEMORY_PART_D | ||||
| SPD_SOURCES += ddr4-spd-empty.hex  # ID = 2(0b0010) | ||||
| SPD_SOURCES += ddr4-spd-3.hex      # ID = 2(0b0010)  Parts = MEMORY_PART_C | ||||
| ``` | ||||
| NOTE: Empty entries may be required if there is a gap created by a memory part | ||||
| with a fixed id. | ||||
|  | ||||
| ### Note of caution | ||||
|  | ||||
| This program assigns DRAM IDs using the order of DRAM part names | ||||
| provided in the input file. Thus, when adding a new memory part to the | ||||
| list, it should always go to the end of the input text file. This | ||||
| guarantees that the memory parts that were already assigned IDs do not | ||||
| change. | ||||
|  | ||||
| ## How to build the tools? | ||||
| ``` | ||||
| # go build gen_spd.go | ||||
| # go build gen_part_id.go | ||||
| ``` | ||||
|  | ||||
| ## How to use the tools? | ||||
| ``` | ||||
| # ./gen_spd <spd_dir> <mem_parts_list_json> <platform> | ||||
| # ./gen_part_id <spd_dir> <makefile_dir> <mem_parts_used_file> | ||||
| ``` | ||||
|  | ||||
| ## Example Usage | ||||
| ``` | ||||
| # ./gen_spd ../../../../src/soc/intel/tigerlake/spd/ddr4 ./global_ddr4_mem_parts.json.txt 'TGL' | ||||
|  | ||||
| ``` | ||||
|  | ||||
| ### Need to add a new memory part for a board? | ||||
|  | ||||
| * If the memory part is not present in the global list of memory | ||||
|   parts, then add the memory part name and attributes as per the | ||||
|   datasheet to the file containing the global list. | ||||
|   * Use `gen_spd.go` with input as the file containing the global list | ||||
|     of memory parts to generate de-duplicated SPDs. | ||||
|   * If a new SPD file is generated, use `git add` to add it to the | ||||
|     tree and push a CL for review. | ||||
| * Update the file containing memory parts used by board (variant) to | ||||
|   add the new memory part name at the end of the file. | ||||
|   * Use gen_part_id.go providing it pointer to the location where SPD | ||||
|     files are stored and file containing the list of memory parts used | ||||
|     by the board(variant). | ||||
|   * Use `git add` to add `Makefile.inc` and `dram_id.generated.txt` | ||||
|     with updated changes and push a CL for review. | ||||
| @@ -1,308 +0,0 @@ | ||||
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||||
|  | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"encoding/csv" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * This program allocates DRAM strap IDs for different parts that are being used by the variant. | ||||
|  * | ||||
|  * It expects the following inputs: | ||||
|  *  Pointer to SPD directory. This is the location where SPD files and SPD Manifest generated by | ||||
|  *  gen_spd.go are placed. | ||||
|  *  Pointer to Makefile directory. Makefile.inc generated by this program is placed in this | ||||
|  *  location. | ||||
|  *  Text file containing a list of memory parts names used by the board. Each line in the file | ||||
|  *  is expected to have one memory part name. | ||||
|  */ | ||||
| const ( | ||||
| 	SPDManifestFileName = "ddr4_spd_manifest.generated.txt" | ||||
| 	MakefileName        = "Makefile.inc" | ||||
| 	DRAMIdFileName      = "dram_id.generated.txt" | ||||
| 	MaxMemoryId         = 15 | ||||
| ) | ||||
|  | ||||
| func usage() { | ||||
| 	fmt.Printf("\nUsage: %s <spd_dir> <makefile_dir> <mem_parts_used_file>\n\n", os.Args[0]) | ||||
| 	fmt.Printf("   where,\n") | ||||
| 	fmt.Printf("   spd_dir = Directory path containing SPD files and manifest generated by gen_spd.go\n") | ||||
| 	fmt.Printf("   makefile_dir = Directory path where generated Makefile.inc should be placed\n") | ||||
| 	fmt.Printf("   mem_parts_used_file = CSV file containing list of memory parts used by the board and optional fixed ids\n\n\n") | ||||
| } | ||||
|  | ||||
| func checkArgs() error { | ||||
|  | ||||
| 	for _, arg := range os.Args[1:] { | ||||
| 		if _, err := os.Stat(arg); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type usedPart struct { | ||||
| 	partName string | ||||
| 	index    int | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Read input file CSV that contains list of memory part names used by the variant | ||||
|  * and an optional assigned id. | ||||
|  */ | ||||
| func readParts(memPartsUsedFileName string) ([]usedPart, error) { | ||||
|  | ||||
| 	f, err := os.Open(memPartsUsedFileName) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer f.Close() | ||||
| 	r := csv.NewReader(f) | ||||
| 	r.FieldsPerRecord = -1 // Allow variable length records | ||||
| 	r.TrimLeadingSpace = true | ||||
| 	r.Comment = '#' | ||||
|  | ||||
| 	parts := []usedPart{} | ||||
|  | ||||
| 	for { | ||||
| 		fields, err := r.Read() | ||||
|  | ||||
| 		if err == io.EOF { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		if len(fields) == 1 { | ||||
| 			parts = append(parts, usedPart{fields[0], -1}) | ||||
| 		} else if len(fields) == 2 { | ||||
| 			assignedId, err := strconv.Atoi(fields[1]) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			if assignedId > MaxMemoryId || assignedId < 0 { | ||||
| 				return nil, fmt.Errorf("Out of bounds assigned id %d for part %s", assignedId, fields[0]) | ||||
| 			} | ||||
| 			parts = append(parts, usedPart{fields[0], assignedId}) | ||||
| 		} else { | ||||
| 			return nil, fmt.Errorf("mem_parts_used_file file is incorrectly formatted") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return parts, nil | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Read SPD manifest file(CSV) generated by gen_spd program and generate two maps: | ||||
|  * 1. Part to SPD Map : This maps global memory part name to generated SPD file name | ||||
|  * 2. SPD to Index Map: This generates a map of deduplicated SPD file names to index assigned to | ||||
|  *                      that SPD. This function sets index for all SPDs to -1. This index gets | ||||
|  *                      updated as part of genPartIdInfo() depending upon the SPDs actually used | ||||
|  *                      by the variant. | ||||
|  */ | ||||
| func readSPDManifest(SPDDirName string) (map[string]string, map[string]int, error) { | ||||
| 	f, err := os.Open(filepath.Join(SPDDirName, SPDManifestFileName)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer f.Close() | ||||
| 	r := csv.NewReader(f) | ||||
|  | ||||
| 	partToSPDMap := make(map[string]string) | ||||
| 	SPDToIndexMap := make(map[string]int) | ||||
|  | ||||
| 	for { | ||||
| 		fields, err := r.Read() | ||||
|  | ||||
| 		if err == io.EOF { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
|  | ||||
| 		if len(fields) != 2 { | ||||
| 			return nil, nil, fmt.Errorf("CSV file is incorrectly formatted") | ||||
| 		} | ||||
|  | ||||
| 		partToSPDMap[fields[0]] = fields[1] | ||||
| 		SPDToIndexMap[fields[1]] = -1 | ||||
| 	} | ||||
|  | ||||
| 	return partToSPDMap, SPDToIndexMap, nil | ||||
| } | ||||
|  | ||||
| /* Print information about memory part used by variant and ID assigned to it. */ | ||||
| func appendPartIdInfo(s *string, partName string, index int) { | ||||
| 	*s += fmt.Sprintf("%-30s %d (%04b)\n", partName, index, int64(index)) | ||||
| } | ||||
|  | ||||
| type partIds struct { | ||||
| 	SPDFileName string | ||||
| 	memParts    string | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * For each part used by variant, check if the SPD (as per the manifest) already has an ID | ||||
|  * assigned to it. If yes, then add the part name to the list of memory parts supported by the | ||||
|  * SPD entry. If not, then assign the next ID to the SPD file and add the part name to the | ||||
|  * list of memory parts supported by the SPD entry. | ||||
|  * | ||||
|  * Returns list of partIds that contains spdFileName and supported memory parts for each | ||||
|  * assigned ID. | ||||
|  */ | ||||
| func genPartIdInfo(parts []usedPart, partToSPDMap map[string]string, SPDToIndexMap map[string]int, makefileDirName string) ([]partIds, error) { | ||||
|  | ||||
| 	partIdList := []partIds{} | ||||
| 	var s string | ||||
|  | ||||
| 	// Assign parts with fixed ids first | ||||
| 	for _, p := range parts { | ||||
|  | ||||
| 		if p.index == -1 { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if p.partName == "" { | ||||
| 			return nil, fmt.Errorf("Invalid part entry") | ||||
| 		} | ||||
|  | ||||
| 		SPDFileName, ok := partToSPDMap[p.partName] | ||||
| 		if !ok { | ||||
| 			return nil, fmt.Errorf("Failed to find part ", p.partName, " in SPD Manifest. Please add the part to global part list and regenerate SPD Manifest") | ||||
| 		} | ||||
|  | ||||
| 		// Extend partIdList with empty entries if needed | ||||
| 		for i := len(partIdList) - 1; i < p.index; i++ { | ||||
| 			partIdList = append(partIdList, partIds{}) | ||||
| 		} | ||||
|  | ||||
| 		if partIdList[p.index].SPDFileName != "" { | ||||
| 			return nil, fmt.Errorf("Part ", p.partName, " is assigned to an already assigned ID ", p.index) | ||||
| 		} | ||||
|  | ||||
| 		partIdList[p.index] = partIds{SPDFileName: SPDFileName, memParts: p.partName} | ||||
|  | ||||
| 		// SPDToIndexMap should point to first assigned index in the used part list | ||||
| 		if SPDToIndexMap[SPDFileName] < 0 { | ||||
| 			SPDToIndexMap[SPDFileName] = p.index | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	s += fmt.Sprintf("%-30s %s\n", "DRAM Part Name", "ID to assign") | ||||
|  | ||||
| 	// Assign parts with no fixed id | ||||
| 	for _, p := range parts { | ||||
| 		if p.partName == "" { | ||||
| 			return nil, fmt.Errorf("Invalid part entry") | ||||
| 		} | ||||
|  | ||||
| 		// Add assigned parts to dram id file in the order they appear | ||||
| 		if p.index != -1 { | ||||
| 			appendPartIdInfo(&s, p.partName, p.index) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		SPDFileName, ok := partToSPDMap[p.partName] | ||||
| 		if !ok { | ||||
| 			return nil, fmt.Errorf("Failed to find part ", p.partName, " in SPD Manifest. Please add the part to global part list and regenerate SPD Manifest") | ||||
| 		} | ||||
|  | ||||
| 		index := SPDToIndexMap[SPDFileName] | ||||
| 		if index != -1 { | ||||
| 			partIdList[index].memParts += ", " + p.partName | ||||
| 			appendPartIdInfo(&s, p.partName, index) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// Find first empty index | ||||
| 		for i, partId := range partIdList { | ||||
| 			if partId.SPDFileName == "" { | ||||
| 				index = i | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Append new entry | ||||
| 		if index == -1 { | ||||
| 			index = len(partIdList) | ||||
| 			partIdList = append(partIdList, partIds{}) | ||||
| 		} | ||||
|  | ||||
| 		SPDToIndexMap[SPDFileName] = index | ||||
| 		appendPartIdInfo(&s, p.partName, index) | ||||
| 		partIdList[index] = partIds{SPDFileName: SPDFileName, memParts: p.partName} | ||||
| 	} | ||||
|  | ||||
| 	fmt.Printf("%s", s) | ||||
| 	err := ioutil.WriteFile(filepath.Join(makefileDirName, DRAMIdFileName), []byte(s), 0644) | ||||
|  | ||||
| 	return partIdList, err | ||||
| } | ||||
|  | ||||
| var generatedCodeLicense string = "## SPDX-License-Identifier: GPL-2.0-or-later" | ||||
| var autoGeneratedInfo string = "## This is an auto-generated file. Do not edit!!" | ||||
|  | ||||
| /* | ||||
|  * This function generates Makefile.inc under the variant directory path and adds assigned SPDs | ||||
|  * to SPD_SOURCES. | ||||
|  */ | ||||
| func genMakefile(partIdList []partIds, makefileDirName string) error { | ||||
| 	var s string | ||||
|  | ||||
| 	s += fmt.Sprintf("%s\n%s\n\n", generatedCodeLicense, autoGeneratedInfo) | ||||
| 	s += fmt.Sprintf("SPD_SOURCES =\n") | ||||
|  | ||||
| 	for i := 0; i < len(partIdList); i++ { | ||||
| 		if partIdList[i].SPDFileName == "" { | ||||
| 			s += fmt.Sprintf("SPD_SOURCES += %s ", "ddr4-spd-empty.hex") | ||||
| 			s += fmt.Sprintf("     # ID = %d(0b%04b)\n", i, int64(i)) | ||||
| 		} else { | ||||
| 			s += fmt.Sprintf("SPD_SOURCES += %s ", partIdList[i].SPDFileName) | ||||
| 			s += fmt.Sprintf("     # ID = %d(0b%04b) ", i, int64(i)) | ||||
| 			s += fmt.Sprintf(" Parts = %04s\n", partIdList[i].memParts) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return ioutil.WriteFile(filepath.Join(makefileDirName, MakefileName), []byte(s), 0644) | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	if len(os.Args) != 4 { | ||||
| 		usage() | ||||
| 		log.Fatal("Incorrect number of arguments") | ||||
| 	} | ||||
|  | ||||
| 	SPDDir, MakefileDir, MemPartsUsedFile := os.Args[1], os.Args[2], os.Args[3] | ||||
|  | ||||
| 	partToSPDMap, SPDToIndexMap, err := readSPDManifest(SPDDir) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	parts, err := readParts(MemPartsUsedFile) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	partIdList, err := genPartIdInfo(parts, partToSPDMap, SPDToIndexMap, MakefileDir) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if err := genMakefile(partIdList, MakefileDir); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,254 +0,0 @@ | ||||
| // Global list of ddr4 memory part attributes. | ||||
| // These attributes match the part specifications and are independent | ||||
| // of any SoC expectations. | ||||
| { | ||||
|     "parts": [ | ||||
|         { | ||||
|             "name": "H5AN8G6NDJR-XNC", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT40A512M16TB-062E:J", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "H5ANAG6NCMR-XNC", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. 1.1 / Sep.2017 | ||||
|             "name": "HMA851S6CJR6N-VK", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 2666, | ||||
|                 "CL_nRCD_nRP": 19, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. 1.5, Apr. 2017 | ||||
|             "name": "K4A8G165WC-BCTD", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 2666, | ||||
|                 "CL_nRCD_nRP": 19, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. 1.3 / Jun.2018 | ||||
|             "name": "H5AN8G6NCJR-VKC", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 2666, | ||||
|                 "CL_nRCD_nRP": 19, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision:  Rev. F 10/17 EN | ||||
|             "name": "MT40A1G16KNR-075:E", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 2666, | ||||
|                 "CL_nRCD_nRP": 18, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. 1.4, Jul. 2017 | ||||
|             "name": "K4AAG165WB-MCTD", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 2666, | ||||
|                 "CL_nRCD_nRP": 19, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 8, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. 1.0 / Aug.2018 | ||||
|             "name": "H5ANAG6NCMR-VKC", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 2666, | ||||
|                 "CL_nRCD_nRP": 19, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. 0.5, Jun. 2019 | ||||
|             "name": "K4A8G165WC-BCWE", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. G 08/2020 EN | ||||
|             "name": "MT40A1G16KD-062E:E", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22, | ||||
|                 "capacityPerDieGb": 16, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1, | ||||
|                 // Table 158 - Refersh Timing - 16Gb | ||||
|                 "TRFC1MinPs": 350000, | ||||
|                 "TRFC2MinPs": 260000, | ||||
|                 "TRFC4MinPs": 160000 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. 0.5, Feb. 2019 | ||||
|             "name": "K4AAG165WA-BCWE", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22, | ||||
|                 "capacityPerDieGb": 16, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1, | ||||
|                 // Table 57 - 16Gb | ||||
|                 "TRFC1MinPs": 350000, | ||||
|                 "TRFC2MinPs": 260000, | ||||
|                 "TRFC4MinPs": 160000 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. 1.5 / Mar.2019 | ||||
|             "name": "H5AN8G6NCJR-XNC", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. 1.0, Dec. 2019 | ||||
|             "name": "K4AAG165WA-BCTD", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 2666, | ||||
|                 "CL_nRCD_nRP": 19, | ||||
|                 "capacityPerDieGb": 16, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1, | ||||
|                 // Table 41 - Note: Both 550ns and 350ns tRFC1 is supported | ||||
|                 "TRFC1MinPs": 350000, | ||||
|                 "TRFC2MinPs": 260000, | ||||
|                 "TRFC4MinPs": 160000 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. 1.0, Feb. 2020 | ||||
|             "name": "H5ANAG6NDMR-XNC", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. 1.4, May. 2020 | ||||
|             "name": "H5ANAG6NCJR-XNC", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22, | ||||
|                 "capacityPerDieGb": 16, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. 0.0, Apr. 2020 | ||||
|             "name": "K4AAG165WB-BCWE", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22, | ||||
|                 "capacityPerDieGb": 16, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             // Datasheet Revision: Rev. A 03/2021 EN | ||||
|             "name": "MT40A1G16RC-062E:B", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22, | ||||
|                 "capacityPerDieGb": 16, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT40A512M16TB-062E:R", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "4JQA-0622AD", | ||||
|             "attribs": { | ||||
|                 "speedMTps": 3200, | ||||
|                 "CL_nRCD_nRP": 22, | ||||
|                 "capacityPerDieGb": 8, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "packageBusWidth": 16, | ||||
|                 "ranksPerPackage": 1 | ||||
|             } | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| @@ -1,11 +0,0 @@ | ||||
| Tools for generating SPD files for DDR4 memory used in platforms with | ||||
| memory down configuration. | ||||
|  | ||||
|  | ||||
|  | ||||
| * _gen_spd.go_ - Generates de-duplicated SPD files using a | ||||
|                  global memory part list provided by the | ||||
|                  mainboard in JSON format. `Go` | ||||
|  | ||||
| * _gen_part_id.go_ - Allocates DRAM strap IDs for different | ||||
|                      DDR4 memory parts used by the board. `Go` | ||||
| @@ -1,266 +0,0 @@ | ||||
| # LPDDR4x SPD tools README | ||||
|  | ||||
| Tools for generating SPD files for LPDDR4x memory used in memory down | ||||
| configurations on Intel Tiger Lake (TGL), Jasper Lake (JSL), and Alder | ||||
| Lake (ADL) based platforms. These tools generate SPDs following | ||||
| JESD209-4C specification and Intel recommendations (doc #616599, | ||||
| #610202, #634730) for LPDDR4x SPD. | ||||
|  | ||||
| There are two tools provided that assist TGL, JSL and ADL based | ||||
| mainboards to generate SPDs and Makefile to integrate these SPDs in | ||||
| coreboot build. These tools can also be used to allocate DRAM IDs | ||||
| (configure DRAM hardware straps) for any LPDDR4x memory part used by the | ||||
| board. | ||||
|  | ||||
| * gen_spd.go: Generates de-duplicated SPD files using a global memory | ||||
|   part list provided by the mainboard in JSON format. Additionally, | ||||
|   generates a SPD manifest file(in CSV format) with information about | ||||
|   what memory part from the global list uses which of the generated | ||||
|   SPD files. | ||||
|  | ||||
| * gen_part_id.go: Allocates DRAM strap IDs for different LPDDR4x | ||||
|   memory parts used by the board. Takes as input list of memory parts | ||||
|   used by the board (with one memory part on each line) and the SPD | ||||
|   manifest file generated by gen_spd.go. Generates Makefile.inc for | ||||
|   integrating the generated SPD files in the coreboot build. | ||||
|  | ||||
| ## Tool 1 - gen_spd.go | ||||
|  | ||||
| This program takes as input: | ||||
| * Pointer to directory where the generated SPD files and manifest will | ||||
|   be placed. | ||||
| * JSON file containing a global list of memory parts with their | ||||
|   attributes as per the datasheet. This is the list of all known | ||||
|   LPDDR4x memory parts irrespective of their usage on the board. | ||||
| * SoC platform name for which the SPDs are being generated. Currently | ||||
|   supported platform names are `TGL`, `JSL` and `ADL`. | ||||
|  | ||||
| Input JSON file requires the following two fields for every memory part: | ||||
| * `name`: Name of the memory part | ||||
| * `attribs`: List of attributes of the memory part as per its | ||||
|   datasheet. These attributes match the part specifications and are | ||||
|   independent of any SoC expectations. Tool takes care of translating | ||||
|   the physical attributes of the memory part to match JEDEC and Intel | ||||
|   MRC expectations. | ||||
|  | ||||
| `attribs` field further contains two types of sub-fields: | ||||
| * Mandatory: These attributes have to be provided for a memory part. | ||||
| * Optional: These attributes can be provided by memory part if it wants | ||||
|   to override the defaults. | ||||
|  | ||||
| ### Mandatory `attribs` | ||||
|  | ||||
| * `densityPerChannelGb`: Density in Gb of the physical channel. | ||||
|  | ||||
| * `banks`: Number of banks per physical channel. This is typically 8 | ||||
|   for LPDDR4x memory parts. | ||||
|  | ||||
| * `channelsPerDie`: Number of physical channels per die. Valid values: | ||||
|   `1, 2, 4`. For a part with x16 bit width, number of channels per die | ||||
|   is 1 or 2.  For a part with x8 bit width, number of channels can be | ||||
|   2 or 4 (4 is basically when two dual-channel byte mode devices are | ||||
|   combined as shown in Figure 3 in JESD209-4C). | ||||
|  | ||||
| * `diesPerPackage`: Number of physical dies in each SDRAM | ||||
|   package. As per JESD209-4C, "Standard LPDDR4 package ballmaps | ||||
|   allocate one ZQ ball per die." Thus, number of diesPerPackage is the | ||||
|   number of ZQ balls on the package. | ||||
|  | ||||
| * `bitWidthPerChannel`: Width of each physical channel. Valid values: | ||||
|   `8, 16` bits. | ||||
|  | ||||
| * `ranksPerChannel`: Number of ranks per physical channel. Valid | ||||
|   values: `1, 2`. If the channels across multiple dies share the same | ||||
|   DQ/DQS pins but use a separate CS, then ranks is 2 else it is 1. | ||||
|  | ||||
| * `speedMbps`: Maximum data rate supported by the part in Mbps. Valid | ||||
|   values: `3200, 3733, 4267` Mbps. | ||||
|  | ||||
| ### Optional `attribs` | ||||
|  | ||||
| * `trfcabNs`: Minimum Refresh Recovery Delay Time (tRFCab) for all | ||||
|   banks in nanoseconds. As per JESD209-4C, this is dependent on the | ||||
|   density per channel. Default values used: | ||||
|     * 6Gb : 280ns | ||||
|     * 8Gb : 280ns | ||||
|     * 12Gb: 380ns | ||||
|     * 16Gb: 380ns | ||||
|  | ||||
| * `trfcpbNs`: Minimum Refresh Recovery Delay Time (tRFCab) per | ||||
|   bank in nanoseconds. As per JESD209-4C, this is dependent on the | ||||
|   density per channel. Default values used: | ||||
|     * 6Gb : 140ns | ||||
|     * 8Gb : 140ns | ||||
|     * 12Gb: 190ns | ||||
|     * 16Gb: 190ns | ||||
|  | ||||
| * `trpabMinNs`: Minimum Row Precharge Delay Time (tRPab) for all banks | ||||
|   in nanoseconds. As per JESD209-4C, this is max(21ns, 4nck) which | ||||
|   defaults to `21ns`. | ||||
|  | ||||
| * `trppbMinNs`: Minimum Row Precharge Delay Time (tRPpb) per bank in | ||||
|   nanoseconds. As per JESD209-4C, this is max(18ns, 4nck) which | ||||
|   defaults to `18ns`. | ||||
|  | ||||
| * `tckMinPs`: SDRAM minimum cycle time (tckMin) value in | ||||
|   picoseconds. This is typically calculated based on the `speedMbps` | ||||
|   attribute. `(1 / speedMbps) * 2`. Default values used(taken from | ||||
|   JESD209-4C): | ||||
|     * 4267 Mbps: 468ps | ||||
|     * 3733 Mbps: 535ps | ||||
|     * 3200 Mbps: 625ps | ||||
|  | ||||
| * `tckMaxPs`: SDRAM maximum cycle time (tckMax) value in | ||||
|   picoseconds. Default value used: `31875ps`. As per JESD209-4C, | ||||
|   TCKmax should be 100ns (100000ps) for all speed grades. But the SPD | ||||
|   byte to encode this field is only 1 byte. Hence, the maximum value | ||||
|   that can be encoded is 31875ps. | ||||
|  | ||||
| * `taaMinPs`: Minimum CAS Latency Time(taaMin) in picoseconds. This | ||||
|   value defaults to nck * tckMin, where nck is minimum CAS latency. | ||||
|  | ||||
| * `trcdMinNs`: Minimum RAS# to CAS# Delay Time (tRCDmin) in | ||||
|   nanoseconds. As per JESD209-4C, this is max(18ns, 4nck) which | ||||
|   defaults to `18ns`. | ||||
|  | ||||
| * `casLatencies`: List of CAS latencies supported by the | ||||
|   part. This is dependent on the attrib `speedMbps`. Default values | ||||
|   used: | ||||
|     * 4267: `"6 10 14 20 24 28 32 36"`. | ||||
|     * 3733: `"6 10 14 20 24 28 32"`. | ||||
|     * 3200: `"6 10 14 20 24 28"`. | ||||
|  | ||||
| ### Example JSON file | ||||
| ``` | ||||
| { | ||||
|     "parts": [ | ||||
|         { | ||||
|             "name": "MEMORY_PART_A", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MEMORY_PART_B", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 1, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 3733, | ||||
|                 "casLatencies": "14 20 24 28 32", | ||||
|                 "tckMaxPs": "1250" | ||||
|             } | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Output | ||||
|  | ||||
| This tool generates the following files using the global list of | ||||
| memory parts in JSON format as described above: | ||||
|   * De-duplicated SPDs required for the different memory parts. These | ||||
|     SPD files are named (spd_1.hex, spd_2.hex, spd_3.hex and so on) | ||||
|     and placed in the directory provided as an input to the tool. | ||||
|   * CSV file representing which of the deduplicated SPD files is used | ||||
|     by which memory part. This file is named as | ||||
|     `spd_manifest.generated.txt` and placed in the directory provided | ||||
|     as an input to the tool along with the generated SPD | ||||
|     files. Example CSV file: | ||||
|     ``` | ||||
|     MEMORY_PART_A, spd_1.hex | ||||
|     MEMORY_PART_B, spd_2.hex | ||||
|     MEMORY_PART_C, spd_3.hex | ||||
|     MEMORY_PART_D, spd_2.hex | ||||
|     MEMORY_PART_E, spd_2.hex | ||||
|     ``` | ||||
|  | ||||
| ## Tool 2 - gen_part_id.go | ||||
|  | ||||
| This program takes as input: | ||||
| * Pointer to directory where the SPD files and the manifest file | ||||
|   `spd_manifest.generated.txt` (in CSV format) are placed by | ||||
|   gen_spd.go | ||||
| * File containing list of memory parts used by the board. Each line of | ||||
|   the file is supposed to contain one memory part `name` as present in | ||||
|   the global list of memory parts provided to gen_spd.go | ||||
| * Pointer to directory where the generated Makefile.inc should be | ||||
|   placed by the tool. | ||||
|  | ||||
| ### Output | ||||
|  | ||||
| This program provides the following: | ||||
|  | ||||
| * Prints out the list of DRAM hardware strap IDs that should be | ||||
|   allocated to each memory part listed in the input file. | ||||
| * Makefile.inc is generated in the provided directory to integrate | ||||
|   SPDs generated by gen_spd.go with the coreboot build for the board. | ||||
| * dram_id.generated.txt is generated in the same directory as | ||||
|   Makefile. This contains the part IDs assigned to the different | ||||
|   memory parts. (Useful to integrate in board schematics). | ||||
|  | ||||
| Sample output (dram_id.generated.txt): | ||||
| ``` | ||||
| DRAM Part Name                 ID to assign | ||||
| MEMORY_PART_A                  0 (0000) | ||||
| MEMORY_PART_B                  1 (0001) | ||||
| MEMORY_PART_C                  2 (0010) | ||||
| MEMORY_PART_D                  1 (0001) | ||||
| ``` | ||||
|  | ||||
| Sample Makefile.inc: | ||||
| ``` | ||||
| ## SPDX-License-Identifier: GPL-2.0-or-later | ||||
| ## This is an auto-generated file. Do not edit!! | ||||
|  | ||||
| SPD_SOURCES = | ||||
| SPD_SOURCES += spd_1.hex      # ID = 0(0b0000)  Parts = MEMORY_PART_A | ||||
| SPD_SOURCES += spd_2.hex      # ID = 1(0b0001)  Parts = MEMORY_PART_B, MEMORY_PART_D | ||||
| SPD_SOURCES += spd_3.hex      # ID = 2(0b0010)  Parts = MEMORY_PART_C | ||||
| ``` | ||||
|  | ||||
| ### Note of caution | ||||
|  | ||||
| This program assigns DRAM IDs using the order of DRAM part names | ||||
| provided in the input file. Thus, when adding a new memory part to the | ||||
| list, it should always go to the end of the input text file. This | ||||
| guarantees that the memory parts that were already assigned IDs do not | ||||
| change. | ||||
|  | ||||
| ## How to build the tools? | ||||
| ``` | ||||
| # go build gen_spd.go | ||||
| # go build gen_part_id.go | ||||
| ``` | ||||
|  | ||||
| ## How to use the tools? | ||||
| ``` | ||||
| # ./gen_spd <spd_dir> <mem_parts_list_json> <platform> | ||||
| # ./gen_part_id <spd_dir> <makefile_dir> <mem_parts_used_file> | ||||
| ``` | ||||
|  | ||||
| ### Need to add a new memory part for a board? | ||||
|  | ||||
| * If the memory part is not present in the global list of memory | ||||
|   parts, then add the memory part name and attributes as per the | ||||
|   datasheet to the file containing the global list. | ||||
|   * Use `gen_spd.go` with input as the file containing the global list | ||||
|     of memory parts to generate de-duplicated SPDs. | ||||
|   * If a new SPD file is generated, use `git add` to add it to the | ||||
|     tree and push a CL for review. | ||||
| * Update the file containing memory parts used by board (variant) to | ||||
|   add the new memory part name at the end of the file. | ||||
|   * Use gen_part_id.go providing it pointer to the location where SPD | ||||
|     files are stored and file containing the list of memory parts used | ||||
|     by the board(variant). | ||||
|   * Use `git add` to add `Makefile.inc` and `dram_id.generated.txt` | ||||
|     with updated changes and push a CL for review. | ||||
| @@ -1,214 +0,0 @@ | ||||
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||||
|  | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"encoding/csv" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * This program allocates DRAM strap IDs for different parts that are being used by the variant. | ||||
|  * | ||||
|  * It expects the following inputs: | ||||
|  *  Pointer to SPD directory. This is the location where SPD files and SPD Manifest generated by | ||||
|  *  gen_spd.go are placed. | ||||
|  *  Pointer to Makefile directory. Makefile.inc generated by this program is placed in this | ||||
|  *  location. | ||||
|  *  Text file containing a list of memory parts names used by the board. Each line in the file | ||||
|  *  is expected to have one memory part name. | ||||
|  */ | ||||
| const ( | ||||
| 	SPDManifestFileName = "lp4x_spd_manifest.generated.txt" | ||||
| 	MakefileName        = "Makefile.inc" | ||||
| 	DRAMIdFileName      = "dram_id.generated.txt" | ||||
| ) | ||||
|  | ||||
| func usage() { | ||||
| 	fmt.Printf("\nUsage: %s <spd_dir> <makefile_dir> <mem_parts_used_file>\n\n", os.Args[0]) | ||||
| 	fmt.Printf("   where,\n") | ||||
| 	fmt.Printf("   spd_dir = Directory path containing SPD files and manifest generated by gen_spd.go\n") | ||||
| 	fmt.Printf("   makefile_dir = Directory path where generated Makefile.inc should be placed\n") | ||||
| 	fmt.Printf("   mem_parts_used_file = File containing list of memory parts used by the board\n\n\n") | ||||
| } | ||||
|  | ||||
| func checkArgs() error { | ||||
|  | ||||
| 	for _, arg := range os.Args[1:] { | ||||
| 		if _, err := os.Stat(arg); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Read input file that contains list of memory part names used by the variant (one on a line) | ||||
|  * and split into separate strings for each part name. | ||||
|  */ | ||||
| func readParts(memPartsUsedFileName string) ([]string, error) { | ||||
| 	lines, err := ioutil.ReadFile(memPartsUsedFileName) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	str := string(lines) | ||||
| 	parts := strings.Split(str, "\n") | ||||
|  | ||||
| 	return parts, nil | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Read SPD manifest file(CSV) generated by gen_spd program and generate two maps: | ||||
|  * 1. Part to SPD Map : This maps global memory part name to generated SPD file name | ||||
|  * 2. SPD to Index Map: This generates a map of deduplicated SPD file names to index assigned to | ||||
|  *                      that SPD. This function sets index for all SPDs to -1. This index gets | ||||
|  *                      updated as part of genPartIdInfo() depending upon the SPDs actually used | ||||
|  *                      by the variant. | ||||
|  */ | ||||
| func readSPDManifest(SPDDirName string) (map[string]string, map[string]int, error) { | ||||
| 	f, err := os.Open(filepath.Join(SPDDirName, SPDManifestFileName)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer f.Close() | ||||
| 	r := csv.NewReader(f) | ||||
|  | ||||
| 	partToSPDMap := make(map[string]string) | ||||
| 	SPDToIndexMap := make(map[string]int) | ||||
|  | ||||
| 	for { | ||||
| 		fields, err := r.Read() | ||||
|  | ||||
| 		if err == io.EOF { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
|  | ||||
| 		if len(fields) != 2 { | ||||
| 			return nil, nil, fmt.Errorf("CSV file is incorrectly formatted") | ||||
| 		} | ||||
|  | ||||
| 		partToSPDMap[fields[0]] = fields[1] | ||||
| 		SPDToIndexMap[fields[1]] = -1 | ||||
| 	} | ||||
|  | ||||
| 	return partToSPDMap, SPDToIndexMap, nil | ||||
| } | ||||
|  | ||||
| /* Print information about memory part used by variant and ID assigned to it. */ | ||||
| func appendPartIdInfo(s *string, partName string, index int) { | ||||
| 	*s += fmt.Sprintf("%-30s %d (%04b)\n", partName, index, int64(index)) | ||||
| } | ||||
|  | ||||
| type partIds struct { | ||||
| 	SPDFileName string | ||||
| 	memParts    string | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * For each part used by variant, check if the SPD (as per the manifest) already has an ID | ||||
|  * assigned to it. If yes, then add the part name to the list of memory parts supported by the | ||||
|  * SPD entry. If not, then assign the next ID to the SPD file and add the part name to the | ||||
|  * list of memory parts supported by the SPD entry. | ||||
|  * | ||||
|  * Returns list of partIds that contains spdFileName and supported memory parts for each | ||||
|  * assigned ID. | ||||
|  */ | ||||
| func genPartIdInfo(parts []string, partToSPDMap map[string]string, SPDToIndexMap map[string]int, makefileDirName string) ([]partIds, error) { | ||||
| 	partIdList := []partIds{} | ||||
| 	curId := 0 | ||||
| 	var s string | ||||
|  | ||||
| 	s += fmt.Sprintf("%-30s %s\n", "DRAM Part Name", "ID to assign") | ||||
|  | ||||
| 	for _, p := range parts { | ||||
| 		if p == "" { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		SPDFileName, ok := partToSPDMap[p] | ||||
| 		if !ok { | ||||
| 			return nil, fmt.Errorf("Failed to find part ", p, " in SPD Manifest. Please add the part to global part list and regenerate SPD Manifest") | ||||
| 		} | ||||
|  | ||||
| 		index := SPDToIndexMap[SPDFileName] | ||||
| 		if index != -1 { | ||||
| 			partIdList[index].memParts += ", " + p | ||||
| 			appendPartIdInfo(&s, p, index) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		SPDToIndexMap[SPDFileName] = curId | ||||
|  | ||||
| 		appendPartIdInfo(&s, p, curId) | ||||
| 		entry := partIds{SPDFileName: SPDFileName, memParts: p} | ||||
| 		partIdList = append(partIdList, entry) | ||||
|  | ||||
| 		curId++ | ||||
| 	} | ||||
|  | ||||
| 	fmt.Printf("%s", s) | ||||
| 	err := ioutil.WriteFile(filepath.Join(makefileDirName, DRAMIdFileName), []byte(s), 0644) | ||||
|  | ||||
| 	return partIdList, err | ||||
| } | ||||
|  | ||||
| var generatedCodeLicense string = "## SPDX-License-Identifier: GPL-2.0-or-later" | ||||
| var autoGeneratedInfo string = "## This is an auto-generated file. Do not edit!!" | ||||
|  | ||||
| /* | ||||
|  * This function generates Makefile.inc under the variant directory path and adds assigned SPDs | ||||
|  * to SPD_SOURCES. | ||||
|  */ | ||||
| func genMakefile(partIdList []partIds, makefileDirName string) error { | ||||
| 	var s string | ||||
|  | ||||
| 	s += fmt.Sprintf("%s\n%s\n\n", generatedCodeLicense, autoGeneratedInfo) | ||||
| 	s += fmt.Sprintf("SPD_SOURCES =\n") | ||||
|  | ||||
| 	for i := 0; i < len(partIdList); i++ { | ||||
| 		s += fmt.Sprintf("SPD_SOURCES += %s ", partIdList[i].SPDFileName) | ||||
| 		s += fmt.Sprintf("     # ID = %d(0b%04b) ", i, int64(i)) | ||||
| 		s += fmt.Sprintf(" Parts = %04s\n", partIdList[i].memParts) | ||||
| 	} | ||||
|  | ||||
| 	return ioutil.WriteFile(filepath.Join(makefileDirName, MakefileName), []byte(s), 0644) | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	if len(os.Args) != 4 { | ||||
| 		usage() | ||||
| 		log.Fatal("Incorrect number of arguments") | ||||
| 	} | ||||
|  | ||||
| 	SPDDir, MakefileDir, MemPartsUsedFile := os.Args[1], os.Args[2], os.Args[3] | ||||
|  | ||||
| 	partToSPDMap, SPDToIndexMap, err := readSPDManifest(SPDDir) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	parts, err := readParts(MemPartsUsedFile) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	partIdList, err := genPartIdInfo(parts, partToSPDMap, SPDToIndexMap, MakefileDir) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if err := genMakefile(partIdList, MakefileDir); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
| @@ -1,996 +0,0 @@ | ||||
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||||
|  | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"reflect" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * This program generates de-duplicated SPD files for LPDDR4x memory using the global memory | ||||
|  * part list provided in CSV format. In addition to that, it also generates SPD manifest in CSV | ||||
|  * format that contains entries of type (DRAM part name, SPD file name) which provides the SPD | ||||
|  * file name used by a given DRAM part. | ||||
|  * | ||||
|  * It takes as input: | ||||
|  * Pointer to directory where the generated SPD files will be placed. | ||||
|  * JSON file containing a list of memory parts with their attributes as per datasheet. | ||||
|  */ | ||||
| const ( | ||||
| 	SPDManifestFileName = "lp4x_spd_manifest.generated.txt" | ||||
|  | ||||
| 	PlatformTGLADL = 0 | ||||
| 	PlatformJSL    = 1 | ||||
| 	PlatformCZN    = 2 | ||||
| ) | ||||
|  | ||||
| var platformMap = map[string]int{ | ||||
| 	"TGL": PlatformTGLADL, | ||||
| 	"JSL": PlatformJSL, | ||||
| 	"ADL": PlatformTGLADL, | ||||
| 	"CZN": PlatformCZN, | ||||
| } | ||||
|  | ||||
| var currPlatform int | ||||
|  | ||||
| type memAttributes struct { | ||||
| 	/* Primary attributes - must be provided by JSON file for each part */ | ||||
| 	DensityPerChannelGb int | ||||
| 	Banks               int | ||||
| 	ChannelsPerDie      int | ||||
| 	DiesPerPackage      int | ||||
| 	BitWidthPerChannel  int | ||||
| 	RanksPerChannel     int | ||||
| 	SpeedMbps           int | ||||
|  | ||||
| 	/* | ||||
| 	 * All the following parameters are optional and required only if the part requires | ||||
| 	 * special parameters as per the datasheet. | ||||
| 	 */ | ||||
| 	/* Timing parameters */ | ||||
| 	TRFCABNs   int | ||||
| 	TRFCPBNs   int | ||||
| 	TRPABMinNs int | ||||
| 	TRPPBMinNs int | ||||
| 	TCKMinPs   int | ||||
| 	TCKMaxPs   int | ||||
| 	TAAMinPs   int | ||||
| 	TRCDMinNs  int | ||||
|  | ||||
| 	/* CAS */ | ||||
| 	CASLatencies  string | ||||
| 	CASFirstByte  byte | ||||
| 	CASSecondByte byte | ||||
| 	CASThirdByte  byte | ||||
| } | ||||
|  | ||||
| /* This encodes the density in Gb to SPD values as per JESD 21-C */ | ||||
| var densityGbToSPDEncoding = map[int]byte{ | ||||
| 	4:  0x4, | ||||
| 	6:  0xb, | ||||
| 	8:  0x5, | ||||
| 	12: 0x8, | ||||
| 	16: 0x6, | ||||
| 	24: 0x9, | ||||
| 	32: 0x7, | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Table 3 from JESD209-4C. | ||||
|  * Maps density per physical channel to row-column encoding as per JESD 21-C for a device with | ||||
|  * x16 physical channel. | ||||
|  */ | ||||
| var densityGbx16ChannelToRowColumnEncoding = map[int]byte{ | ||||
| 	4:  0x19, /* 15 rows, 10 columns */ | ||||
| 	6:  0x21, /* 16 rows, 10 columns */ | ||||
| 	8:  0x21, /* 16 rows, 10 columns */ | ||||
| 	12: 0x29, /* 17 rows, 10 columns */ | ||||
| 	16: 0x29, /* 17 rows, 10 columns */ | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Table 5 from JESD209-4C. | ||||
|  * Maps density per physical channel to row-column encoding as per JESD 21-C for a device with | ||||
|  * x8 physical channel. | ||||
|  */ | ||||
| var densityGbx8ChannelToRowColumnEncoding = map[int]byte{ | ||||
| 	3:  0x21, /* 16 rows, 10 columns */ | ||||
| 	4:  0x21, /* 16 rows, 10 columns */ | ||||
| 	6:  0x29, /* 17 rows, 10 columns */ | ||||
| 	8:  0x29, /* 17 rows, 10 columns */ | ||||
| 	12: 0x31, /* 18 rows, 10 columns */ | ||||
| 	16: 0x31, /* 18 rows, 10 columns */ | ||||
| } | ||||
|  | ||||
| type refreshTimings struct { | ||||
| 	TRFCABNs int | ||||
| 	TRFCPBNs int | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Table 112 from JESD209-4C | ||||
|  * Maps density per physical channel to refresh timings. This is the same for x8 and x16 | ||||
|  * devices. | ||||
|  */ | ||||
| var densityGbPhysicalChannelToRefreshEncoding = map[int]refreshTimings{ | ||||
| 	3: { | ||||
| 		TRFCABNs: 180, | ||||
| 		TRFCPBNs: 90, | ||||
| 	}, | ||||
| 	4: { | ||||
| 		TRFCABNs: 180, | ||||
| 		TRFCPBNs: 90, | ||||
| 	}, | ||||
| 	6: { | ||||
| 		TRFCABNs: 280, | ||||
| 		TRFCPBNs: 140, | ||||
| 	}, | ||||
| 	8: { | ||||
| 		TRFCABNs: 280, | ||||
| 		TRFCPBNs: 140, | ||||
| 	}, | ||||
| 	12: { | ||||
| 		TRFCABNs: 380, | ||||
| 		TRFCPBNs: 190, | ||||
| 	}, | ||||
| 	16: { | ||||
| 		TRFCABNs: 380, | ||||
| 		TRFCPBNs: 190, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| type speedParams struct { | ||||
| 	TCKMinPs               int | ||||
| 	TCKMaxPs               int | ||||
| 	CASLatenciesx16Channel string | ||||
| 	CASLatenciesx8Channel  string | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	/* First Byte */ | ||||
| 	CAS6  = 1 << 1 | ||||
| 	CAS10 = 1 << 4 | ||||
| 	CAS14 = 1 << 7 | ||||
| 	/* Second Byte */ | ||||
| 	CAS16 = 1 << 0 | ||||
| 	CAS20 = 1 << 2 | ||||
| 	CAS22 = 1 << 3 | ||||
| 	CAS24 = 1 << 4 | ||||
| 	CAS26 = 1 << 5 | ||||
| 	CAS28 = 1 << 6 | ||||
| 	/* Third Byte */ | ||||
| 	CAS32 = 1 << 0 | ||||
| 	CAS36 = 1 << 2 | ||||
| 	CAS40 = 1 << 4 | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	/* | ||||
| 	 * JEDEC spec says that TCKmax should be 100ns for all speed grades. | ||||
| 	 * 100ns in MTB units comes out to be 0x320. But since this is a byte field, set it to | ||||
| 	 * 0xFF i.e. 31.875ns. | ||||
| 	 */ | ||||
| 	TCKMaxPsDefault = 31875 | ||||
| ) | ||||
|  | ||||
| var speedMbpsToSPDEncoding = map[int]speedParams{ | ||||
| 	4267: { | ||||
| 		TCKMinPs:               468, /* 1/4267 * 2 */ | ||||
| 		TCKMaxPs:               TCKMaxPsDefault, | ||||
| 		CASLatenciesx16Channel: "6 10 14 20 24 28 32 36", | ||||
| 		CASLatenciesx8Channel:  "6 10 16 22 26 32 36 40", | ||||
| 	}, | ||||
| 	3733: { | ||||
| 		TCKMinPs:               535, /* 1/3733 * 2 */ | ||||
| 		TCKMaxPs:               TCKMaxPsDefault, | ||||
| 		CASLatenciesx16Channel: "6 10 14 20 24 28 32", | ||||
| 		CASLatenciesx8Channel:  "6 10 16 22 26 32 36", | ||||
| 	}, | ||||
| 	3200: { | ||||
| 		TCKMinPs:               625, /* 1/3200 * 2 */ | ||||
| 		TCKMaxPs:               TCKMaxPsDefault, | ||||
| 		CASLatenciesx16Channel: "6 10 14 20 24 28", | ||||
| 		CASLatenciesx8Channel:  "6 10 16 22 26 32", | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| var bankEncoding = map[int]byte{ | ||||
| 	4: 0 << 4, | ||||
| 	8: 1 << 4, | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	TGLLogicalChannelWidth = 16 | ||||
| ) | ||||
|  | ||||
| /* Returns density to encode as per Intel MRC expectations. */ | ||||
| func getMRCDensity(memAttribs *memAttributes) int { | ||||
| 	if currPlatform == PlatformTGLADL { | ||||
| 		/* | ||||
| 		 * Intel MRC on TGL expects density per logical channel to be encoded in | ||||
| 		 * SPDIndexDensityBanks. Logical channel on TGL is an x16 channel. | ||||
| 		 */ | ||||
| 		return memAttribs.DensityPerChannelGb * TGLLogicalChannelWidth / memAttribs.BitWidthPerChannel | ||||
| 	} else if currPlatform == PlatformJSL || currPlatform == PlatformCZN { | ||||
| 		/* | ||||
| 		 * Intel MRC on JSL expects density per die to be encoded in | ||||
| 		 * SPDIndexDensityBanks. | ||||
| 		 */ | ||||
| 		return memAttribs.DensityPerChannelGb * memAttribs.ChannelsPerDie | ||||
| 	} | ||||
|  | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func encodeDensityBanks(memAttribs *memAttributes) byte { | ||||
| 	var b byte | ||||
|  | ||||
| 	b = densityGbToSPDEncoding[getMRCDensity(memAttribs)] | ||||
| 	b |= bankEncoding[memAttribs.Banks] | ||||
|  | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func encodeSdramAddressing(memAttribs *memAttributes) byte { | ||||
| 	densityPerChannelGb := memAttribs.DensityPerChannelGb | ||||
| 	if memAttribs.BitWidthPerChannel == 8 { | ||||
| 		return densityGbx8ChannelToRowColumnEncoding[densityPerChannelGb] | ||||
| 	} else { | ||||
| 		return densityGbx16ChannelToRowColumnEncoding[densityPerChannelGb] | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func encodeChannelsPerDie(channels int) byte { | ||||
| 	var temp byte | ||||
|  | ||||
| 	temp = byte(channels >> 1) | ||||
|  | ||||
| 	return temp << 2 | ||||
| } | ||||
|  | ||||
| func encodePackage(dies int) byte { | ||||
| 	var temp byte | ||||
|  | ||||
| 	if dies > 1 { | ||||
| 		/* If more than one die, then this is a non-monolithic device. */ | ||||
| 		temp = 1 | ||||
| 	} else { | ||||
| 		/* If only single die, then this is a monolithic device. */ | ||||
| 		temp = 0 | ||||
| 	} | ||||
|  | ||||
| 	return temp << 7 | ||||
| } | ||||
|  | ||||
| /* Per JESD209-4C Dies = ZQ balls on the package */ | ||||
| /* Note that this can be different than the part's die count */ | ||||
| func encodeDiesPerPackage(memAttribs *memAttributes) byte { | ||||
| 	var dies int = 0 | ||||
| 	if currPlatform == PlatformTGLADL { | ||||
| 		/* Intel MRC expects logical dies to be encoded for TGL. */ | ||||
| 		dies = memAttribs.ChannelsPerDie * memAttribs.RanksPerChannel * memAttribs.BitWidthPerChannel / 16 | ||||
| 	} else if currPlatform == PlatformJSL || currPlatform == PlatformCZN { | ||||
| 		/* Intel MRC expects physical dies to be encoded for JSL. */ | ||||
| 		/* AMD PSP expects physical dies (ZQ balls) */ | ||||
| 		dies = memAttribs.DiesPerPackage | ||||
| 	} | ||||
|  | ||||
| 	b := encodePackage(dies) /* Monolithic / Non-monolithic device */ | ||||
| 	b |= (byte(dies) - 1) << 4 | ||||
|  | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func encodePackageType(memAttribs *memAttributes) byte { | ||||
| 	var b byte | ||||
|  | ||||
| 	b |= encodeChannelsPerDie(memAttribs.ChannelsPerDie) | ||||
| 	b |= encodeDiesPerPackage(memAttribs) | ||||
|  | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func encodeDataWidth(bitWidthPerChannel int) byte { | ||||
| 	return byte(bitWidthPerChannel / 8) | ||||
| } | ||||
|  | ||||
| func encodeRanks(ranks int) byte { | ||||
| 	var b byte | ||||
| 	b = byte(ranks - 1) | ||||
| 	return b << 3 | ||||
| } | ||||
|  | ||||
| func encodeModuleOrganization(memAttribs *memAttributes) byte { | ||||
| 	var b byte | ||||
|  | ||||
| 	b = encodeDataWidth(memAttribs.BitWidthPerChannel) | ||||
| 	b |= encodeRanks(memAttribs.RanksPerChannel) | ||||
|  | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	/* | ||||
| 	 * As per advisory 616599: | ||||
| 	 * 7:5 (Number of system channels) = 000 (1 channel always) | ||||
| 	 * 2:0 (Bus width) = 001 (x16 always) | ||||
| 	 * Set to 0x01. | ||||
| 	 */ | ||||
| 	SPDValueBusWidthTGL = 0x01 | ||||
| 	/* | ||||
| 	 * As per advisory 610202: | ||||
| 	 * 7:5 (Number of system channels) = 001 (2 channel always) | ||||
| 	 * 2:0 (Bus width) = 010 (x32 always) | ||||
| 	 * Set to 0x01. | ||||
| 	 */ | ||||
| 	SPDValueBusWidthJSL = 0x22 | ||||
| ) | ||||
|  | ||||
| func encodeBusWidth(memAttribs *memAttributes) byte { | ||||
| 	if currPlatform == PlatformTGLADL { | ||||
| 		return SPDValueBusWidthTGL | ||||
| 	} else if currPlatform == PlatformJSL || currPlatform == PlatformCZN { | ||||
| 		return SPDValueBusWidthJSL | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func encodeTCKMin(memAttribs *memAttributes) byte { | ||||
| 	return convPsToMtbByte(memAttribs.TCKMinPs) | ||||
| } | ||||
|  | ||||
| func encodeTCKMinFineOffset(memAttribs *memAttributes) byte { | ||||
| 	return convPsToFtbByte(memAttribs.TCKMinPs) | ||||
| } | ||||
|  | ||||
| func encodeTCKMax(memAttribs *memAttributes) byte { | ||||
| 	return convPsToMtbByte(memAttribs.TCKMaxPs) | ||||
| } | ||||
|  | ||||
| func encodeTCKMaxFineOffset(memAttribs *memAttributes) byte { | ||||
| 	return convPsToFtbByte(memAttribs.TCKMaxPs) | ||||
| } | ||||
|  | ||||
| func encodeCASFirstByte(memAttribs *memAttributes) byte { | ||||
| 	return memAttribs.CASFirstByte | ||||
| } | ||||
|  | ||||
| func encodeCASSecondByte(memAttribs *memAttributes) byte { | ||||
| 	return memAttribs.CASSecondByte | ||||
| } | ||||
|  | ||||
| func encodeCASThirdByte(memAttribs *memAttributes) byte { | ||||
| 	return memAttribs.CASThirdByte | ||||
| } | ||||
|  | ||||
| func divRoundUp(dividend int, divisor int) int { | ||||
| 	return (dividend + divisor - 1) / divisor | ||||
| } | ||||
|  | ||||
| func convNsToPs(timeNs int) int { | ||||
| 	return timeNs * 1000 | ||||
| } | ||||
|  | ||||
| func convMtbToPs(mtb int) int { | ||||
| 	return mtb * 125 | ||||
| } | ||||
|  | ||||
| func convPsToMtb(timePs int) int { | ||||
| 	return divRoundUp(timePs, 125) | ||||
| } | ||||
|  | ||||
| func convPsToMtbByte(timePs int) byte { | ||||
| 	return byte(convPsToMtb(timePs) & 0xff) | ||||
| } | ||||
|  | ||||
| func convPsToFtbByte(timePs int) byte { | ||||
| 	mtb := convPsToMtb(timePs) | ||||
| 	ftb := timePs - convMtbToPs(mtb) | ||||
|  | ||||
| 	return byte(ftb) | ||||
| } | ||||
|  | ||||
| func convNsToMtb(timeNs int) int { | ||||
| 	return convPsToMtb(convNsToPs(timeNs)) | ||||
| } | ||||
|  | ||||
| func convNsToMtbByte(timeNs int) byte { | ||||
| 	return convPsToMtbByte(convNsToPs(timeNs)) | ||||
| } | ||||
|  | ||||
| func convNsToFtbByte(timeNs int) byte { | ||||
| 	return convPsToFtbByte(convNsToPs(timeNs)) | ||||
| } | ||||
|  | ||||
| func encodeTAAMin(memAttribs *memAttributes) byte { | ||||
| 	return convPsToMtbByte(memAttribs.TAAMinPs) | ||||
| } | ||||
|  | ||||
| func encodeTAAMinFineOffset(memAttribs *memAttributes) byte { | ||||
| 	return convPsToFtbByte(memAttribs.TAAMinPs) | ||||
| } | ||||
|  | ||||
| func encodeTRCDMin(memAttribs *memAttributes) byte { | ||||
| 	return convNsToMtbByte(memAttribs.TRCDMinNs) | ||||
| } | ||||
|  | ||||
| func encodeTRCDMinFineOffset(memAttribs *memAttributes) byte { | ||||
| 	return convNsToFtbByte(memAttribs.TRCDMinNs) | ||||
| } | ||||
|  | ||||
| func encodeTRPABMin(memAttribs *memAttributes) byte { | ||||
| 	return convNsToMtbByte(memAttribs.TRPABMinNs) | ||||
| } | ||||
|  | ||||
| func encodeTRPABMinFineOffset(memAttribs *memAttributes) byte { | ||||
| 	return convNsToFtbByte(memAttribs.TRPABMinNs) | ||||
| } | ||||
|  | ||||
| func encodeTRPPBMin(memAttribs *memAttributes) byte { | ||||
| 	return convNsToMtbByte(memAttribs.TRPPBMinNs) | ||||
| } | ||||
|  | ||||
| func encodeTRPPBMinFineOffset(memAttribs *memAttributes) byte { | ||||
| 	return convNsToFtbByte(memAttribs.TRPPBMinNs) | ||||
| } | ||||
|  | ||||
| func encodeTRFCABMinMsb(memAttribs *memAttributes) byte { | ||||
| 	return byte((convNsToMtb(memAttribs.TRFCABNs) >> 8) & 0xff) | ||||
| } | ||||
|  | ||||
| func encodeTRFCABMinLsb(memAttribs *memAttributes) byte { | ||||
| 	return byte(convNsToMtb(memAttribs.TRFCABNs) & 0xff) | ||||
| } | ||||
|  | ||||
| func encodeTRFCPBMinMsb(memAttribs *memAttributes) byte { | ||||
| 	return byte((convNsToMtb(memAttribs.TRFCPBNs) >> 8) & 0xff) | ||||
| } | ||||
|  | ||||
| func encodeTRFCPBMinLsb(memAttribs *memAttributes) byte { | ||||
| 	return byte(convNsToMtb(memAttribs.TRFCPBNs) & 0xff) | ||||
| } | ||||
|  | ||||
| type SPDAttribFunc func(*memAttributes) byte | ||||
|  | ||||
| type SPDAttribTableEntry struct { | ||||
| 	constVal byte | ||||
| 	getVal   SPDAttribFunc | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	/* SPD Byte Index */ | ||||
| 	SPDIndexSize                            = 0 | ||||
| 	SPDIndexRevision                        = 1 | ||||
| 	SPDIndexMemoryType                      = 2 | ||||
| 	SPDIndexModuleType                      = 3 | ||||
| 	SPDIndexDensityBanks                    = 4 | ||||
| 	SPDIndexAddressing                      = 5 | ||||
| 	SPDIndexPackageType                     = 6 | ||||
| 	SPDIndexOptionalFeatures                = 7 | ||||
| 	SPDIndexModuleOrganization              = 12 | ||||
| 	SPDIndexBusWidth                        = 13 | ||||
| 	SPDIndexTimebases                       = 17 | ||||
| 	SPDIndexTCKMin                          = 18 | ||||
| 	SPDIndexTCKMax                          = 19 | ||||
| 	SPDIndexCASFirstByte                    = 20 | ||||
| 	SPDIndexCASSecondByte                   = 21 | ||||
| 	SPDIndexCASThirdByte                    = 22 | ||||
| 	SPDIndexCASFourthByte                   = 23 | ||||
| 	SPDIndexTAAMin                          = 24 | ||||
| 	SPDIndexReadWriteLatency                = 25 | ||||
| 	SPDIndexTRCDMin                         = 26 | ||||
| 	SPDIndexTRPABMin                        = 27 | ||||
| 	SPDIndexTRPPBMin                        = 28 | ||||
| 	SPDIndexTRFCABMinLSB                    = 29 | ||||
| 	SPDIndexTRFCABMinMSB                    = 30 | ||||
| 	SPDIndexTRFCPBMinLSB                    = 31 | ||||
| 	SPDIndexTRFCPBMinMSB                    = 32 | ||||
| 	SPDIndexTRPPBMinFineOffset              = 120 | ||||
| 	SPDIndexTRPABMinFineOffset              = 121 | ||||
| 	SPDIndexTRCDMinFineOffset               = 122 | ||||
| 	SPDIndexTAAMinFineOffset                = 123 | ||||
| 	SPDIndexTCKMaxFineOffset                = 124 | ||||
| 	SPDIndexTCKMinFineOffset                = 125 | ||||
| 	SPDIndexManufacturerPartNumberStartByte = 329 | ||||
| 	SPDIndexManufacturerPartNumberEndByte   = 348 | ||||
|  | ||||
| 	/* SPD Byte Value */ | ||||
|  | ||||
| 	/* | ||||
| 	 * From JEDEC spec: | ||||
| 	 * 6:4 (Bytes total) = 2 (512 bytes) | ||||
| 	 * 3:0 (Bytes used) = 3 (384 bytes) | ||||
| 	 * Set to 0x23 for LPDDR4x. | ||||
| 	 */ | ||||
| 	SPDValueSize = 0x23 | ||||
|  | ||||
| 	/* | ||||
| 	 * From JEDEC spec: Revision 1.1 | ||||
| 	 * Set to 0x11. | ||||
| 	 */ | ||||
| 	SPDValueRevision = 0x11 | ||||
|  | ||||
| 	/* LPDDR4x memory type = 0x11 */ | ||||
| 	SPDValueMemoryType = 0x11 | ||||
|  | ||||
| 	/* | ||||
| 	 * From JEDEC spec: | ||||
| 	 * 7:7 (Hybrid) = 0 (Not hybrid) | ||||
| 	 * 6:4 (Hybrid media) = 000 (Not hybrid) | ||||
| 	 * 3:0 (Base Module Type) = 1110 (Non-DIMM solution) | ||||
| 	 * | ||||
| 	 * This is dependent on hardware design. LPDDR4x only has memory down solution. | ||||
| 	 * Hence this is not hybrid non-DIMM solution. | ||||
| 	 * Set to 0x0E. | ||||
| 	 */ | ||||
| 	SPDValueModuleType = 0x0e | ||||
|  | ||||
| 	/* | ||||
| 	 * From JEDEC spec: | ||||
| 	 * 5:4 (Maximum Activate Window) = 00 (8192 * tREFI) | ||||
| 	 * 3:0 (Maximum Activate Count) = 1000 (Unlimited MAC) | ||||
| 	 * | ||||
| 	 * Needs to come from datasheet, but most parts seem to support unlimited MAC. | ||||
| 	 * MR#24 OP3 | ||||
| 	 */ | ||||
| 	SPDValueOptionalFeatures = 0x08 | ||||
|  | ||||
| 	/* | ||||
| 	 * From JEDEC spec: | ||||
| 	 * 3:2 (MTB) = 00 (0.125ns) | ||||
| 	 * 1:0 (FTB) = 00 (1ps) | ||||
| 	 * Set to 0x00. | ||||
| 	 */ | ||||
| 	SPDValueTimebases = 0x00 | ||||
|  | ||||
| 	/* CAS fourth byte: All bits are reserved */ | ||||
| 	SPDValueCASFourthByte = 0x00 | ||||
|  | ||||
| 	/* Write Latency Set A and Read Latency DBI-RD disabled. */ | ||||
| 	SPDValueReadWriteLatency = 0x00 | ||||
|  | ||||
| 	/* As per JEDEC spec, unused digits of manufacturer part number are left as blank. */ | ||||
| 	SPDValueManufacturerPartNumberBlank = 0x20 | ||||
| ) | ||||
|  | ||||
| var SPDAttribTable = map[int]SPDAttribTableEntry{ | ||||
| 	SPDIndexSize:               {constVal: SPDValueSize}, | ||||
| 	SPDIndexRevision:           {constVal: SPDValueRevision}, | ||||
| 	SPDIndexMemoryType:         {constVal: SPDValueMemoryType}, | ||||
| 	SPDIndexModuleType:         {constVal: SPDValueModuleType}, | ||||
| 	SPDIndexDensityBanks:       {getVal: encodeDensityBanks}, | ||||
| 	SPDIndexAddressing:         {getVal: encodeSdramAddressing}, | ||||
| 	SPDIndexPackageType:        {getVal: encodePackageType}, | ||||
| 	SPDIndexOptionalFeatures:   {constVal: SPDValueOptionalFeatures}, | ||||
| 	SPDIndexModuleOrganization: {getVal: encodeModuleOrganization}, | ||||
| 	SPDIndexBusWidth:           {getVal: encodeBusWidth}, | ||||
| 	SPDIndexTimebases:          {constVal: SPDValueTimebases}, | ||||
| 	SPDIndexTCKMin:             {getVal: encodeTCKMin}, | ||||
| 	SPDIndexTCKMax:             {getVal: encodeTCKMax}, | ||||
| 	SPDIndexTCKMaxFineOffset:   {getVal: encodeTCKMaxFineOffset}, | ||||
| 	SPDIndexTCKMinFineOffset:   {getVal: encodeTCKMinFineOffset}, | ||||
| 	SPDIndexCASFirstByte:       {getVal: encodeCASFirstByte}, | ||||
| 	SPDIndexCASSecondByte:      {getVal: encodeCASSecondByte}, | ||||
| 	SPDIndexCASThirdByte:       {getVal: encodeCASThirdByte}, | ||||
| 	SPDIndexCASFourthByte:      {constVal: SPDValueCASFourthByte}, | ||||
| 	SPDIndexTAAMin:             {getVal: encodeTAAMin}, | ||||
| 	SPDIndexTAAMinFineOffset:   {getVal: encodeTAAMinFineOffset}, | ||||
| 	SPDIndexReadWriteLatency:   {constVal: SPDValueReadWriteLatency}, | ||||
| 	SPDIndexTRCDMin:            {getVal: encodeTRCDMin}, | ||||
| 	SPDIndexTRCDMinFineOffset:  {getVal: encodeTRCDMinFineOffset}, | ||||
| 	SPDIndexTRPABMin:           {getVal: encodeTRPABMin}, | ||||
| 	SPDIndexTRPABMinFineOffset: {getVal: encodeTRPABMinFineOffset}, | ||||
| 	SPDIndexTRPPBMin:           {getVal: encodeTRPPBMin}, | ||||
| 	SPDIndexTRPPBMinFineOffset: {getVal: encodeTRPPBMinFineOffset}, | ||||
| 	SPDIndexTRFCABMinLSB:       {getVal: encodeTRFCABMinLsb}, | ||||
| 	SPDIndexTRFCABMinMSB:       {getVal: encodeTRFCABMinMsb}, | ||||
| 	SPDIndexTRFCPBMinLSB:       {getVal: encodeTRFCPBMinLsb}, | ||||
| 	SPDIndexTRFCPBMinMSB:       {getVal: encodeTRFCPBMinMsb}, | ||||
| } | ||||
|  | ||||
| type memParts struct { | ||||
| 	MemParts []memPart `json:"parts"` | ||||
| } | ||||
|  | ||||
| type memPart struct { | ||||
| 	Name        string | ||||
| 	Attribs     memAttributes | ||||
| 	SPDFileName string | ||||
| } | ||||
|  | ||||
| func writeSPDManifest(memParts *memParts, SPDDirName string) error { | ||||
| 	var s string | ||||
|  | ||||
| 	fmt.Printf("Generating SPD Manifest with following entries:\n") | ||||
|  | ||||
| 	for i := 0; i < len(memParts.MemParts); i++ { | ||||
| 		fmt.Printf("%-40s %s\n", memParts.MemParts[i].Name, memParts.MemParts[i].SPDFileName) | ||||
| 		s += fmt.Sprintf("%s,%s\n", memParts.MemParts[i].Name, memParts.MemParts[i].SPDFileName) | ||||
| 	} | ||||
|  | ||||
| 	return ioutil.WriteFile(filepath.Join(SPDDirName, SPDManifestFileName), []byte(s), 0644) | ||||
| } | ||||
|  | ||||
| func isManufacturerPartNumberByte(index int) bool { | ||||
| 	if index >= SPDIndexManufacturerPartNumberStartByte && index <= SPDIndexManufacturerPartNumberEndByte { | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func getSPDByte(index int, memAttribs *memAttributes) byte { | ||||
| 	e, ok := SPDAttribTable[index] | ||||
| 	if ok == false { | ||||
| 		if isManufacturerPartNumberByte(index) { | ||||
| 			return SPDValueManufacturerPartNumberBlank | ||||
| 		} | ||||
| 		return 0x00 | ||||
| 	} | ||||
|  | ||||
| 	if e.getVal != nil { | ||||
| 		return e.getVal(memAttribs) | ||||
| 	} | ||||
|  | ||||
| 	return e.constVal | ||||
| } | ||||
|  | ||||
| func createSPD(memAttribs *memAttributes) string { | ||||
| 	var s string | ||||
|  | ||||
| 	for i := 0; i < 512; i++ { | ||||
| 		b := getSPDByte(i, memAttribs) | ||||
|  | ||||
| 		if (i+1)%16 == 0 { | ||||
| 			s += fmt.Sprintf("%02X\n", b) | ||||
| 		} else { | ||||
| 			s += fmt.Sprintf("%02X ", b) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return s | ||||
| } | ||||
|  | ||||
| func dedupeMemoryPart(dedupedParts []*memPart, memPart *memPart) bool { | ||||
| 	for i := 0; i < len(dedupedParts); i++ { | ||||
| 		if reflect.DeepEqual(dedupedParts[i].Attribs, memPart.Attribs) { | ||||
| 			memPart.SPDFileName = dedupedParts[i].SPDFileName | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func generateSPD(memPart *memPart, SPDId int, SPDDirName string) { | ||||
| 	s := createSPD(&memPart.Attribs) | ||||
| 	memPart.SPDFileName = fmt.Sprintf("lp4x-spd-%d.hex", SPDId) | ||||
| 	ioutil.WriteFile(filepath.Join(SPDDirName, memPart.SPDFileName), []byte(s), 0644) | ||||
| } | ||||
|  | ||||
| func readMemoryParts(memParts *memParts, memPartsFileName string) error { | ||||
| 	databytes, err := ioutil.ReadFile(memPartsFileName) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return json.Unmarshal(databytes, memParts) | ||||
| } | ||||
|  | ||||
| func validateDensityx8Channel(densityPerChannelGb int) error { | ||||
| 	if _, ok := densityGbx8ChannelToRowColumnEncoding[densityPerChannelGb]; ok == false { | ||||
| 		return fmt.Errorf("Incorrect x8 density: ", densityPerChannelGb, "Gb") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func validateDensityx16Channel(densityPerChannelGb int) error { | ||||
| 	if _, ok := densityGbx16ChannelToRowColumnEncoding[densityPerChannelGb]; ok == false { | ||||
| 		return fmt.Errorf("Incorrect x16 density: ", densityPerChannelGb, "Gb") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func validateDensity(memAttribs *memAttributes) error { | ||||
| 	if memAttribs.BitWidthPerChannel == 8 { | ||||
| 		return validateDensityx8Channel(memAttribs.DensityPerChannelGb) | ||||
| 	} else if memAttribs.BitWidthPerChannel == 16 { | ||||
| 		return validateDensityx16Channel(memAttribs.DensityPerChannelGb) | ||||
| 	} | ||||
|  | ||||
| 	return fmt.Errorf("No density table for this bit width: ", memAttribs.BitWidthPerChannel) | ||||
| } | ||||
|  | ||||
| func validateBanks(banks int) error { | ||||
| 	if banks != 4 && banks != 8 { | ||||
| 		return fmt.Errorf("Incorrect banks: ", banks) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func validateChannels(channels int) error { | ||||
| 	if channels != 1 && channels != 2 && channels != 4 { | ||||
| 		return fmt.Errorf("Incorrect channels per die: ", channels) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func validateDataWidth(width int) error { | ||||
| 	if width != 8 && width != 16 { | ||||
| 		return fmt.Errorf("Incorrect bit width: ", width) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func validateRanks(ranks int) error { | ||||
| 	if ranks != 1 && ranks != 2 { | ||||
| 		return fmt.Errorf("Incorrect ranks: ", ranks) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func validateSpeed(speed int) error { | ||||
| 	if _, ok := speedMbpsToSPDEncoding[speed]; ok == false { | ||||
| 		return fmt.Errorf("Incorrect speed: ", speed, " Mbps") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func validateMemoryParts(memParts *memParts) error { | ||||
| 	for i := 0; i < len(memParts.MemParts); i++ { | ||||
| 		if err := validateBanks(memParts.MemParts[i].Attribs.Banks); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := validateChannels(memParts.MemParts[i].Attribs.ChannelsPerDie); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := validateDataWidth(memParts.MemParts[i].Attribs.BitWidthPerChannel); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := validateDensity(&memParts.MemParts[i].Attribs); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := validateRanks(memParts.MemParts[i].Attribs.RanksPerChannel); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := validateSpeed(memParts.MemParts[i].Attribs.SpeedMbps); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func encodeLatencies(latency int, memAttribs *memAttributes) error { | ||||
| 	switch latency { | ||||
| 	case 6: | ||||
| 		memAttribs.CASFirstByte |= CAS6 | ||||
| 	case 10: | ||||
| 		memAttribs.CASFirstByte |= CAS10 | ||||
| 	case 14: | ||||
| 		memAttribs.CASFirstByte |= CAS14 | ||||
| 	case 16: | ||||
| 		memAttribs.CASSecondByte |= CAS16 | ||||
| 	case 20: | ||||
| 		memAttribs.CASSecondByte |= CAS20 | ||||
| 	case 22: | ||||
| 		memAttribs.CASSecondByte |= CAS22 | ||||
| 	case 24: | ||||
| 		memAttribs.CASSecondByte |= CAS24 | ||||
| 	case 26: | ||||
| 		memAttribs.CASSecondByte |= CAS26 | ||||
| 	case 28: | ||||
| 		memAttribs.CASSecondByte |= CAS28 | ||||
| 	case 32: | ||||
| 		memAttribs.CASThirdByte |= CAS32 | ||||
| 	case 36: | ||||
| 		memAttribs.CASThirdByte |= CAS36 | ||||
| 	case 40: | ||||
| 		memAttribs.CASThirdByte |= CAS40 | ||||
| 	default: | ||||
| 		fmt.Errorf("Incorrect CAS Latency: ", latency) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func updateTCK(memAttribs *memAttributes) { | ||||
| 	if memAttribs.TCKMinPs == 0 { | ||||
| 		memAttribs.TCKMinPs = speedMbpsToSPDEncoding[memAttribs.SpeedMbps].TCKMinPs | ||||
| 	} | ||||
| 	if memAttribs.TCKMaxPs == 0 { | ||||
| 		memAttribs.TCKMaxPs = speedMbpsToSPDEncoding[memAttribs.SpeedMbps].TCKMaxPs | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func getCASLatencies(memAttribs *memAttributes) string { | ||||
| 	if memAttribs.BitWidthPerChannel == 16 { | ||||
| 		return speedMbpsToSPDEncoding[memAttribs.SpeedMbps].CASLatenciesx16Channel | ||||
| 	} else if memAttribs.BitWidthPerChannel == 8 { | ||||
| 		return speedMbpsToSPDEncoding[memAttribs.SpeedMbps].CASLatenciesx8Channel | ||||
| 	} | ||||
|  | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func updateCAS(memAttribs *memAttributes) error { | ||||
| 	if len(memAttribs.CASLatencies) == 0 { | ||||
| 		memAttribs.CASLatencies = getCASLatencies(memAttribs) | ||||
| 	} | ||||
|  | ||||
| 	latencies := strings.Fields(memAttribs.CASLatencies) | ||||
| 	for i := 0; i < len(latencies); i++ { | ||||
| 		latency, err := strconv.Atoi(latencies[i]) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("Unable to convert latency ", latencies[i]) | ||||
| 		} | ||||
| 		if err := encodeLatencies(latency, memAttribs); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func getMinCAS(memAttribs *memAttributes) (int, error) { | ||||
| 	if (memAttribs.CASThirdByte & CAS40) != 0 { | ||||
| 		return 40, nil | ||||
| 	} | ||||
| 	if (memAttribs.CASThirdByte & CAS36) != 0 { | ||||
| 		return 36, nil | ||||
| 	} | ||||
| 	if (memAttribs.CASThirdByte & CAS32) != 0 { | ||||
| 		return 32, nil | ||||
| 	} | ||||
| 	if (memAttribs.CASSecondByte & CAS28) != 0 { | ||||
| 		return 28, nil | ||||
| 	} | ||||
|  | ||||
| 	return 0, fmt.Errorf("Unexpected min CAS") | ||||
| } | ||||
|  | ||||
| func updateTAAMin(memAttribs *memAttributes) error { | ||||
| 	if memAttribs.TAAMinPs == 0 { | ||||
| 		minCAS, err := getMinCAS(memAttribs) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		memAttribs.TAAMinPs = memAttribs.TCKMinPs * minCAS | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func updateTRFCAB(memAttribs *memAttributes) { | ||||
| 	if memAttribs.TRFCABNs == 0 { | ||||
| 		memAttribs.TRFCABNs = densityGbPhysicalChannelToRefreshEncoding[memAttribs.DensityPerChannelGb].TRFCABNs | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func updateTRFCPB(memAttribs *memAttributes) { | ||||
| 	if memAttribs.TRFCPBNs == 0 { | ||||
| 		memAttribs.TRFCPBNs = densityGbPhysicalChannelToRefreshEncoding[memAttribs.DensityPerChannelGb].TRFCPBNs | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func updateTRCD(memAttribs *memAttributes) { | ||||
| 	if memAttribs.TRCDMinNs == 0 { | ||||
| 		/* JEDEC spec says max of 18ns */ | ||||
| 		memAttribs.TRCDMinNs = 18 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func updateTRPAB(memAttribs *memAttributes) { | ||||
| 	if memAttribs.TRPABMinNs == 0 { | ||||
| 		/* JEDEC spec says max of 21ns */ | ||||
| 		memAttribs.TRPABMinNs = 21 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func updateTRPPB(memAttribs *memAttributes) { | ||||
| 	if memAttribs.TRPPBMinNs == 0 { | ||||
| 		/* JEDEC spec says max of 18ns */ | ||||
| 		memAttribs.TRPPBMinNs = 18 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func normalizeMemoryAttributes(memAttribs *memAttributes) { | ||||
| 	if currPlatform == PlatformTGLADL { | ||||
| 		/* | ||||
| 		 * TGL does not really use physical organization of dies per package when | ||||
| 		 * generating the SPD. So, set it to 0 here so that deduplication ignores | ||||
| 		 * that field. | ||||
| 		 */ | ||||
| 		memAttribs.DiesPerPackage = 0 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func updateMemoryAttributes(memAttribs *memAttributes) error { | ||||
| 	updateTCK(memAttribs) | ||||
| 	if err := updateCAS(memAttribs); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := updateTAAMin(memAttribs); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	updateTRFCAB(memAttribs) | ||||
| 	updateTRFCPB(memAttribs) | ||||
| 	updateTRCD(memAttribs) | ||||
| 	updateTRPAB(memAttribs) | ||||
| 	updateTRPPB(memAttribs) | ||||
|  | ||||
| 	normalizeMemoryAttributes(memAttribs) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func isPlatformSupported(platform string) error { | ||||
| 	var ok bool | ||||
|  | ||||
| 	currPlatform, ok = platformMap[platform] | ||||
| 	if ok == false { | ||||
| 		return fmt.Errorf("Unsupported platform: ", platform) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func usage() { | ||||
| 	fmt.Printf("\nUsage: %s <spd_dir> <mem_parts_list_json> <platform>\n\n", os.Args[0]) | ||||
| 	fmt.Printf("   where,\n") | ||||
| 	fmt.Printf("   spd_dir = Directory path containing SPD files and manifest generated by gen_spd.go\n") | ||||
| 	fmt.Printf("   mem_parts_list_json = JSON File containing list of memory parts and attributes\n") | ||||
| 	fmt.Printf("   platform = SoC Platform for which the SPDs are being generated\n") | ||||
| 	fmt.Printf("              supported platforms: ") | ||||
| 	keys := reflect.ValueOf(platformMap).MapKeys() | ||||
| 	fmt.Println(keys) | ||||
| 	fmt.Printf("\n\n\n") | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	if len(os.Args) != 4 { | ||||
| 		usage() | ||||
| 		log.Fatal("Incorrect number of arguments") | ||||
| 	} | ||||
|  | ||||
| 	var memParts memParts | ||||
| 	var dedupedParts []*memPart | ||||
|  | ||||
| 	SPDDir, GlobalMemPartsFile, Platform := os.Args[1], os.Args[2], strings.ToUpper(os.Args[3]) | ||||
|  | ||||
| 	if err := isPlatformSupported(Platform); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if err := readMemoryParts(&memParts, GlobalMemPartsFile); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if err := validateMemoryParts(&memParts); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	SPDId := 1 | ||||
|  | ||||
| 	for i := 0; i < len(memParts.MemParts); i++ { | ||||
| 		if err := updateMemoryAttributes(&memParts.MemParts[i].Attribs); err != nil { | ||||
| 			log.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		if dedupeMemoryPart(dedupedParts, &memParts.MemParts[i]) == false { | ||||
| 			generateSPD(&memParts.MemParts[i], SPDId, SPDDir) | ||||
| 			SPDId++ | ||||
| 			dedupedParts = append(dedupedParts, &memParts.MemParts[i]) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err := writeSPDManifest(&memParts, SPDDir); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
| @@ -1,344 +0,0 @@ | ||||
| { | ||||
|     "parts": [ | ||||
|         { | ||||
|             "name": "H9HCNNNBKMMLXR-NEE", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "H9HCNNNFAMMLXR-NEE", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 4, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 8, | ||||
|                 "ranksPerChannel": 2, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "K4U6E3S4AA-MGCL", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "K4UBE3D4AA-MGCL", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 2, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT53E1G32D2NP-046 WT:A", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 16, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT53E1G32D2NP-046 WT:B", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 2, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "H9HKNNNCRMBVAR-NEH", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT53E1G64D4SQ-046 WT:A", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 16, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT53E512M32D2NP-046 WT:F", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "NT6AP256T32AV-J2", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 4, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 3733, | ||||
|                 "tckMaxPs": 1250, | ||||
|                 "casLatencies": "14 20 24 28 32" | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "K4U6E3S4AA-MGCR", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT53E512M32D2NP-046 WT:E", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "H9HCNNNCPMMLXR-NEE", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 2, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "K4UBE3D4AA-MGCR", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 2, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT53E512M64D4NW-046 WT:E", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT53E1G64D8NW-046 WT:E", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 4, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 2, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "H9HCNNNCRMBLPR-NEE", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "H9HCNNNFBMBLPR-NEE", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 4, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 2, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT53D1G64D4NW-046 WT:A", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 16, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT53D512M64D4NW-046 WT:F", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "NT6AP256T32AV-J1", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 4, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267, | ||||
|                 "tckMaxPs": 1250, | ||||
|                 "casLatencies": "14 20 24 28 32 36" | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT53E1G32D4NQ-046 WT:E", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 2, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT53E2G32D4NQ-046 WT:A", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 16, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 2, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "MT53E512M32D1NP-046 WT:B", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "H54G46CYRBX267", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "H54G56CYRBX247", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 2, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "K4U6E3S4AB-MGCL", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 1, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 1, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "name": "K4UBE3D4AB-MGCL", | ||||
|             "attribs": { | ||||
|                 "densityPerChannelGb": 8, | ||||
|                 "banks": 8, | ||||
|                 "channelsPerDie": 2, | ||||
|                 "diesPerPackage": 2, | ||||
|                 "bitWidthPerChannel": 16, | ||||
|                 "ranksPerChannel": 2, | ||||
|                 "speedMbps": 4267 | ||||
|             } | ||||
|         } | ||||
|     ] | ||||
| } | ||||
		Reference in New Issue
	
	Block a user