diff options
author | Rob Barnes <robbarnes@google.com> | 2020-08-26 12:47:27 -0600 |
---|---|---|
committer | Furquan Shaikh <furquan@google.com> | 2020-08-27 20:14:34 +0000 |
commit | 196e9c002122d3469ab486d2f40af1e66dcdf067 (patch) | |
tree | 0dff97ec9060bd6c056a7c49f96e64e7ce15812d /util/spd_tools/ddr4 | |
parent | 1860cd460aaf3dabb58d996b81d51916949e59b3 (diff) |
util/spd_tools: Remove intel subfolder
Move ddr4 and lp4x to spd_tools root folder. The tool now applies to non
intel platforms.
BUG=b:162939176
TEST=Run tool
Signed-off-by: Rob Barnes <robbarnes@google.com>
Change-Id: I0941ea036d760ee27eb34f259f4506a4b7584bee
Reviewed-on: https://review.coreboot.org/c/coreboot/+/44844
Reviewed-by: Furquan Shaikh <furquan@google.com>
Reviewed-by: Nick Vaccaro <nvaccaro@google.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Diffstat (limited to 'util/spd_tools/ddr4')
-rw-r--r-- | util/spd_tools/ddr4/README.md | 275 | ||||
-rw-r--r-- | util/spd_tools/ddr4/gen_part_id.go | 215 | ||||
-rw-r--r-- | util/spd_tools/ddr4/gen_spd.go | 1386 | ||||
-rw-r--r-- | util/spd_tools/ddr4/global_ddr4_mem_parts.json.txt | 37 |
4 files changed, 1913 insertions, 0 deletions
diff --git a/util/spd_tools/ddr4/README.md b/util/spd_tools/ddr4/README.md new file mode 100644 index 0000000000..31e326d442 --- /dev/null +++ b/util/spd_tools/ddr4/README.md @@ -0,0 +1,275 @@ +# DDR4 SPD tools README + +Tools for generating SPD files for DDR4 memory used in memory down +configurations on Intel Tiger Lake (TGL) based +platforms. These tools generate SPDs following JESD79-4C +and Jedec 4.1.2.L-5 R29 v103 specifications. + +There are two tools provided that assist TGL 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 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 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 + 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`. + +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. + +* `deviceBusWidth`: 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, + "deviceBusWidth": 16, + "ranksPerPackage": 1, + } + }, + { + "name": "MEMORY_PART_B", + "attribs": { + "speedMTps": 3200, + "CL_nRCD_nRP": 22 + "capacityPerDieGb": 8, + "diesPerPackage": 1, + "deviceBusWidth": 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 + `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 + `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 += 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-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> +``` + +## 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. diff --git a/util/spd_tools/ddr4/gen_part_id.go b/util/spd_tools/ddr4/gen_part_id.go new file mode 100644 index 0000000000..f67b4a9434 --- /dev/null +++ b/util/spd_tools/ddr4/gen_part_id.go @@ -0,0 +1,215 @@ +/* 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 = "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("MEMORY_TYPE = ddr4\n\n") + 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) + } +} diff --git a/util/spd_tools/ddr4/gen_spd.go b/util/spd_tools/ddr4/gen_spd.go new file mode 100644 index 0000000000..5adadc962a --- /dev/null +++ b/util/spd_tools/ddr4/gen_spd.go @@ -0,0 +1,1386 @@ +/* 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 DDR4 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 = "spd_manifest.generated.txt" + + PlatformTGL = 0 +) + +var platformMap = map[string]int { + "TGL": PlatformTGL, +} + +var currPlatform int + +type memAttributes struct { + /* Primary attributes - must be provided by JSON file for each part */ + SpeedMTps int + CL_nRCD_nRP int + CapacityPerDieGb int + DiesPerPackage int + DeviceBusWidth int + RanksPerPackage int + + /* + * All the following parameters are optional and required only if the part requires + * special parameters as per the datasheet. + */ + /* Timing parameters */ + TAAMinPs int + TRCDMinPs int + TRPMinPs int + TRASMinPs int + TRCMinPs int + TCKMinPs int + TCKMaxPs int + TRFC1MinPs int + TRFC2MinPs int + TRFC4MinPs int + TFAWMinPs int + TRRDLMinPs int + TRRDSMinPs int + TCCDLMinPs int + TWRMinPs int + TWTRLMinPs int + TWTRSMinPs int + + /* CAS */ + CASLatencies string + CASFirstByte byte + CASSecondByte byte + CASThirdByte byte + CASFourthByte byte +} + +/* This encodes the density in Gb to SPD low nibble value as per JESD 4.1.2.L-5 R29 */ +var densityGbToSPDEncoding = map[int]byte { + 2: 0x3, + 4: 0x4, + 8: 0x5, + 16: 0x6, +} + +/* + * Tables 4 thru Table 7 from JESD79-4C. + * Maps density per die to row-column encoding for a device with x8/x16 + * physical channel. + */ +var densityGbx8x16DieCapacityToRowColumnEncoding = map[int]byte { + 2: 0x11, /* 14 rows, 10 columns */ + 4: 0x19, /* 15 rows, 10 columns */ + 8: 0x21, /* 16 rows, 10 columns */ + 16: 0x29, /* 17 rows, 10 columns */ +} + +/* + * Tables 169 & 170 in the JESD79-4C spec + * Maps die density to refresh timings. This is the same for x8 and x16 + * devices. + */ + +/* maps die density to rcf1 timing in pico seconds */ +var tRFC1Encoding = map[int]int { + 2: 160000, + 4: 260000, + 8: 350000, + 16: 550000, +} + +/* maps die density to rcf2 timing in pico seconds */ +var tRFC2Encoding = map[int]int { + 2: 110000, + 4: 160000, + 8: 260000, + 16: 350000, +} + +/* maps die density to rcf4 timing in pico seconds */ +var tRFC4Encoding = map[int]int { + 2: 90000, + 4: 110000, + 8: 160000, + 16: 260000, +} + +func getTRCMinPs(memAttribs *memAttributes) int { + return memAttribs.TAAMinPs + memAttribs.TRASMinPs +} + +func getDefaultTCKMinPs(memAttribs *memAttributes) int { + /* value 2000000 = 2 * 1000000, where 1000000 is to convert mS to pS */ + return 2000000 / memAttribs.SpeedMTps +} + +type speedBinAttributes struct { + TRASMinPs int + TCKMaxPs int +} + +var speedBinToSPDEncoding = map[int]speedBinAttributes { + 1600: { + TRASMinPs: 35000, + TCKMaxPs: 1500, + }, + 1866: { + TRASMinPs: 34000, + TCKMaxPs: 1250, + }, + 2133: { + TRASMinPs: 33000, + TCKMaxPs: 1071, + }, + 2400: { + TRASMinPs: 32000, + TCKMaxPs: 937, + }, + 2666: { + TRASMinPs: 32000, + TCKMaxPs: 833, + }, + 2933: { + TRASMinPs: 32000, + TCKMaxPs: 750, + }, + 3200: { + TRASMinPs: 32000, + TCKMaxPs: 682, + }, +} + +func getBankGroups(memAttribs *memAttributes) byte { + var bg byte + + switch memAttribs.DeviceBusWidth { + case 8: + bg = 4 + case 16: + if memAttribs.DiesPerPackage == 1 { + bg = 2 /* x16 SDP has 2 bank groups */ + } else { + bg = 4 /* x16 DDP has 4 bank groups */ + } + } + + return bg +} + +func encodeBankGroups(bg byte) byte { + var val byte + + switch bg { + case 2: + val = 1 + case 4: + val = 2 + } + + return val << 6 +} + +func encodeDensityBanks(memAttribs *memAttributes) byte { + var b byte + + b = densityGbToSPDEncoding[memAttribs.CapacityPerDieGb] + b |= encodeBankGroups(getBankGroups(memAttribs)) + /* No need to encode banksPerGroup.it's always 4 ([4:5] = 0) */ + + return b +} + +func encodeSdramAddressing(memAttribs *memAttributes) byte { + var b byte + + b = densityGbx8x16DieCapacityToRowColumnEncoding[memAttribs.CapacityPerDieGb] + + return b +} + +func encodePackageDeviceType(dies int) byte { + var b byte + + if dies > 1 { + /* If more than one die, then this is a non-monolithic device. */ + b = 1 + } else { + /* If only single die, then this is a monolithic device. */ + b = 0 + } + + return b << 7 +} + +func encodeSignalLoadingFromDieCount(dies int) byte { + var loading byte + + /* + * If die count = 1, signal loading = "not specified" = 0 + * If die count > 1, signal loading = "multi" = 2 + */ + if dies == 1 { + loading = 0 + } else { + loading = 1 + } + + return loading +} + +func encodeDiesPerPackage(dies int) byte { + var b byte + + b = encodePackageDeviceType(dies) /* Monolithic / Non-monolithic device */ + b |= (byte(dies) - 1) << 4 + + return b +} + +func encodePackageType(memAttribs *memAttributes) byte { + var b byte + + b = encodeDiesPerPackage(memAttribs.DiesPerPackage) + b |= encodeSignalLoadingFromDieCount(memAttribs.DiesPerPackage) + + return b +} + +func encodeDataWidth(bitWidthPerDevice int) byte { + var width byte + + switch bitWidthPerDevice { + case 8: + width = 1 + case 16: + width = 2 + } + + return width +} + +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.DeviceBusWidth) + b |= encodeRanks(memAttribs.RanksPerPackage) + + return b +} + +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 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 encodeTAAMin(memAttribs *memAttributes) byte { + return convPsToMtbByte(memAttribs.TAAMinPs) +} + +func encodeTAAMinFineOffset(memAttribs *memAttributes) byte { + return convPsToFtbByte(memAttribs.TAAMinPs) +} + +func encodeTRCDMin(memAttribs *memAttributes) byte { + return convPsToMtbByte(memAttribs.TRCDMinPs) +} + +func encodeTRCDMinFineOffset(memAttribs *memAttributes) byte { + return convPsToFtbByte(memAttribs.TRCDMinPs) +} + +func encodeTRPMin(memAttribs *memAttributes) byte { + return convPsToMtbByte(memAttribs.TRPMinPs) +} + +func encodeTRCMinFineOffset(memAttribs *memAttributes) byte { + return convPsToFtbByte(memAttribs.TRCMinPs) +} + +func encodeTRPMinFineOffset(memAttribs *memAttributes) byte { + return convPsToFtbByte(memAttribs.TRPMinPs) +} + +func encodeTRASRCMinMSNs(memAttribs *memAttributes) byte { + var b byte + + b = byte((convPsToMtb(memAttribs.TRASMinPs) >> 4) & 0xf0) + b |= byte((convPsToMtb(memAttribs.TRCMinPs) >> 8) & 0x0f) + + return b +} + +func encodeTRASMinLsb(memAttribs *memAttributes) byte { + return byte(convPsToMtb(memAttribs.TRASMinPs) & 0xff) +} + +func encodeTRCMinLsb(memAttribs *memAttributes) byte { + return byte(convPsToMtb(memAttribs.TRCMinPs) & 0xff) +} + +var pageSizefromBusWidthEncoding = map[int]int { + 8: 1, + 16: 2, +} + +/* + * Per Table 69 & Table 70 of Jedec JESD79-4C + * tFAW timing is based on : + * Speed bin and page size + */ +func getTFAWMinPs(memAttribs *memAttributes) int { + var tFAWFixed int + + if pageSizefromBusWidthEncoding[memAttribs.DeviceBusWidth] == 1 { + switch memAttribs.SpeedMTps { + case 1600: + tFAWFixed = 25000 + case 1866: + tFAWFixed = 23000 + default: + tFAWFixed = 21000 + } + } else if pageSizefromBusWidthEncoding[memAttribs.DeviceBusWidth] == 2 { + switch memAttribs.SpeedMTps { + case 1600: + tFAWFixed = 35000 + default: + tFAWFixed = 30000 + } + } + + return tFAWFixed +} + +/* Update settings based on data sheet (json) supplied memory attributes */ + +func updateTFAWMin(memAttribs *memAttributes) { + var tFAWFromTck int + + if memAttribs.TFAWMinPs == 0 { + memAttribs.TFAWMinPs = getTFAWMinPs(memAttribs) + } + + switch pageSizefromBusWidthEncoding[memAttribs.DeviceBusWidth] { + case 1: + tFAWFromTck = 20 * memAttribs.TCKMinPs + case 2: + tFAWFromTck = 28 * memAttribs.TCKMinPs + } + + if memAttribs.TFAWMinPs < tFAWFromTck { + memAttribs.TFAWMinPs = tFAWFromTck + } +} + +func updateTRFC1Min(memAttribs *memAttributes) { + if memAttribs.TRFC1MinPs == 0 { + memAttribs.TRFC1MinPs = tRFC1Encoding[memAttribs.CapacityPerDieGb] + } +} + +func updateTRFC2Min(memAttribs *memAttributes) { + if memAttribs.TRFC2MinPs == 0 { + memAttribs.TRFC2MinPs = tRFC2Encoding[memAttribs.CapacityPerDieGb] + } +} + +func updateTRFC4Min(memAttribs *memAttributes) { + if memAttribs.TRFC4MinPs == 0 { + memAttribs.TRFC4MinPs = tRFC4Encoding[memAttribs.CapacityPerDieGb] + } +} + +func getTRRDLMinPs(memAttribs *memAttributes) int { + var tRRDLFixed int + + /* + * Per JESD79-4C Tables 169 & 170, tRRD_L is based on : + * Speed bin and page size + */ + switch pageSizefromBusWidthEncoding[memAttribs.DeviceBusWidth] { + case 1: + switch memAttribs.SpeedMTps { + case 1600: + tRRDLFixed = 6000 + default: + tRRDLFixed = 5300 + } + case 2: + switch memAttribs.SpeedMTps { + case 1600: + tRRDLFixed = 7500 + default: + tRRDLFixed = 6400 + } + } + + return tRRDLFixed +} + +func updateTRRDLMin(memAttribs *memAttributes) { + var tRRDLFromTck int + + if memAttribs.TRRDLMinPs == 0 { + memAttribs.TRRDLMinPs= getTRRDLMinPs(memAttribs) + } + + tRRDLFromTck = 4 * memAttribs.TCKMinPs + + if memAttribs.TRRDLMinPs < tRRDLFromTck { + memAttribs.TRRDLMinPs = tRRDLFromTck + } +} + +var speedToTRRDSMinPsOneKPageSize = map[int]int { + 1600: 5000, + 1866: 4200, + 2133: 3700, + 2400: 3300, + 2666: 3000, + 2933: 2700, + 3200: 2500, +} + +var speedToTRRDSMinPsTwoKPageSize = map[int]int { + 1600: 6000, + 1866: 5300, + 2133: 5300, + 2400: 5300, + 2666: 5300, + 2933: 5300, + 3200: 5300, +} + +func getTRRDSMinPs(memAttribs *memAttributes) int { + var tRRDFixed int + + switch pageSizefromBusWidthEncoding[memAttribs.DeviceBusWidth] { + case 1: + tRRDFixed = speedToTRRDSMinPsOneKPageSize[memAttribs.SpeedMTps] + case 2: + tRRDFixed = speedToTRRDSMinPsTwoKPageSize[memAttribs.SpeedMTps] + } + + return tRRDFixed +} + +func updateTRRDSMin(memAttribs *memAttributes) { + var tRRDFromTck int + + if memAttribs.TRRDSMinPs == 0 { + memAttribs.TRRDSMinPs = getTRRDSMinPs(memAttribs) + } + + tRRDFromTck = 4 * memAttribs.TCKMinPs + + if memAttribs.TRRDSMinPs < tRRDFromTck { + memAttribs.TRRDSMinPs = tRRDFromTck + } +} + +/* + * Per JESD79-4C Tables 169 and 170, + * tCCD_L is based on : + * Speed Bin + */ +func getTCCDLMinPs(memAttribs *memAttributes) int { + var tCCDLFixed int + + switch memAttribs.SpeedMTps { + case 1600: + tCCDLFixed = 6250 + case 1866: + tCCDLFixed = 5355 + case 2133: + tCCDLFixed = 5355 + default: + tCCDLFixed = 5000 + } + + return tCCDLFixed +} + +func updateTCCDLMin(memAttribs *memAttributes) { + var tCCDLFromTck int + + if memAttribs.TCCDLMinPs == 0 { + memAttribs.TCCDLMinPs = getTCCDLMinPs(memAttribs) + } + + tCCDLFromTck = 5 * memAttribs.TCKMinPs + + if memAttribs.TCCDLMinPs < tCCDLFromTck { + memAttribs.TCCDLMinPs = tCCDLFromTck + } +} + +func encodeTRFC1MinLsb(memAttribs *memAttributes) byte { + var mtb int + + mtb = convPsToMtb(memAttribs.TRFC1MinPs) + + return byte(mtb & 0xff) +} + +func encodeTRFC1MinMsb(memAttribs *memAttributes) byte { + var mtb int + + mtb = convPsToMtb(memAttribs.TRFC1MinPs) + + return byte((mtb >> 8) & 0xff) +} + +func encodeTRFC2MinLsb(memAttribs *memAttributes) byte { + var mtb int + + mtb = convPsToMtb(memAttribs.TRFC2MinPs) + + return byte(mtb & 0xff) +} + +func encodeTRFC2MinMsb(memAttribs *memAttributes) byte { + var mtb int + + mtb = convPsToMtb(memAttribs.TRFC2MinPs) + + return byte((mtb >> 8) & 0xff) +} + +func encodeTRFC4MinLsb(memAttribs *memAttributes) byte { + var mtb int + + mtb = convPsToMtb(memAttribs.TRFC4MinPs) + + return byte(mtb & 0xff) +} + +func encodeTRFC4MinMsb(memAttribs *memAttributes) byte { + var mtb int + + mtb = convPsToMtb(memAttribs.TRFC4MinPs) + + return byte((mtb >> 8) & 0xff) +} + +func encodeTFAWMinMSN(memAttribs *memAttributes) byte { + var mtb int + + mtb = convPsToMtb(memAttribs.TFAWMinPs) + + return byte((mtb >> 8) & 0x0f) +} + +func encodeTFAWMinLsb(memAttribs *memAttributes) byte { + var mtb int + + mtb = convPsToMtb(memAttribs.TFAWMinPs) + + return byte(mtb & 0xff) +} + +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 encodeCASFourthByte(memAttribs *memAttributes) byte { + return memAttribs.CASFourthByte +} + +func encodeTRRDSMin(memAttribs *memAttributes) byte { + return convPsToMtbByte(memAttribs.TRRDSMinPs) +} + +func encodeTRRDSMinFineOffset(memAttribs *memAttributes) byte { + return convPsToFtbByte(memAttribs.TRRDSMinPs) +} + +func encodeTRRDLMin(memAttribs *memAttributes) byte { + return convPsToMtbByte(memAttribs.TRRDLMinPs) +} + +func encodeTRRDLMinFineOffset(memAttribs *memAttributes) byte { + return convPsToFtbByte(memAttribs.TRRDLMinPs) +} + +func encodeTCCDLMin(memAttribs *memAttributes) byte { + return convPsToMtbByte(memAttribs.TCCDLMinPs) +} + +func encodeTCCDLMinFineOffset(memAttribs *memAttributes) byte { + return convPsToFtbByte(memAttribs.TCCDLMinPs) +} + +func encodeTWRMinMSN(memAttribs *memAttributes) byte { + return byte((convPsToMtb(TimingValueTWRMinPs) >> 8) & 0x0f) +} + +func encodeTWRMinLsb(memAttribs *memAttributes) byte { + return byte(convPsToMtb(TimingValueTWRMinPs) & 0xff) +} + +func encodeTWTRMinMSNs(memAttribs *memAttributes) byte { + var b byte + + b = byte((convPsToMtb(memAttribs.TWTRLMinPs) >> 4) & 0xf0) + b |= byte((convPsToMtb(memAttribs.TWTRSMinPs) >> 8) & 0x0f) + + return b +} + +func encodeTWTRSMinLsb(memAttribs *memAttributes) byte { + return byte(convPsToMtb(memAttribs.TWTRSMinPs) & 0xff) +} + +func encodeTWTRLMinLsb(memAttribs *memAttributes) byte { + return byte(convPsToMtb(memAttribs.TWTRLMinPs) & 0xff) +} + +type SPDMemAttribFunc func (*memAttributes) byte +type SPDConvConstFunc func () byte + +type SPDAttribTableEntry struct { + constVal byte + getVal SPDMemAttribFunc +} + +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 + SPDIndexTRCDMin = 25 + SPDIndexTRPMin = 26 + SPDIndexTRASRCMinMSNs = 27 + SPDIndexTRASMinLsb = 28 + SPDIndexTRCMinLsb = 29 + SPDIndexTRFC1MinLsb = 30 + SPDIndexTRFC1MinMsb = 31 + SPDIndexTRFC2MinLsb = 32 + SPDIndexTRFC2MinMsb = 33 + SPDIndexTRFC4MinLsb = 34 + SPDIndexTRFC4MinMsb = 35 + SPDIndexTFAWMinMSN = 36 + SPDIndexTFAWMinLsb = 37 + SPDIndexTRRDSMin = 38 + SPDIndexTRRDLMin = 39 + SPDIndexTCCDLMin = 40 + SPDIndexTWRMinMSN = 41 + SPDIndexTWRMinLsb = 42 + SPDIndexTWTRMinMSNs = 43 + SPDIndexWTRSMinLsb = 44 + SPDIndexWTRLMinLsb = 45 + SPDIndexTCCDLMinFineOffset = 117 + SPDIndexTRRDLMinFineOffset = 118 + SPDIndexTRRDSMinFineOffset = 119 + SPDIndexTRCMinFineOffset = 120 + SPDIndexTRPMinFineOffset = 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 DDR4. + */ + SPDValueSize = 0x23 + + /* + * From JEDEC spec: Revision 1.1 + * Set to 0x11. + */ + SPDValueRevision = 0x11 + + /* DDR4 memory type = 0x0C */ + SPDValueMemoryType = 0x0C + + /* + * From JEDEC spec: + * Module Type [0:3] : + * 0 = Undefined + * 1 = RDIMM (width = 133.35 mm nom) + * 2 = UDIMM (width = 133.35 mm nom) + * 3 = SO-DIMM (width = 68.60 mm nom) + * 4 = LRDIMM (width = 133.35 mm nom) + * + * DDR4 on TGL uses SO-DIMM type for for both memory down and DIMM config. + * Set to 0x03. + */ + SPDValueModuleType = 0x03 + + /* + * 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: + * 2:0 Primary Bus Width in Bits = 011 (x64 always) + * Set to 0x03. + */ + SPDValueModuleBusWidth = 0x03 + + /* + * 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 + + /* As per JEDEC spec, unused digits of manufacturer part number are left as blank. */ + SPDValueManufacturerPartNumberBlank = 0x20 + +) + +const ( + /* + * As per Table 75 of Jedec spec 4.1.20-L-5 R29 v103: + * tWRMin = 15nS for all DDR4 Speed Bins + * Set to 15000 pS + */ + TimingValueTWRMinPs = 15000 + + /* + * As per Table 78 of Jedec spec 4.1.20-L-5 R29 v103: + * tWTR_SMin = 2.5nS for all DDR4 Speed Bins + * Set to 2500 pS + */ + TimingValueTWTRSMinPs = 2500 + + /* + * As per Table 80 of Jedec spec 4.1.20-L-5 R29 v103: + * tWTR_LMin = 7.5 nS for all DDR4 Speed Bins + * Set to 7500 pS + */ + TimingValueTWTRLMinPs = 7500 +) + +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: { constVal: SPDValueModuleBusWidth }, + SPDIndexTimebases: { constVal: SPDValueTimebases }, + SPDIndexTCKMin: { getVal: encodeTCKMin }, + SPDIndexTCKMinFineOffset: { getVal: encodeTCKMinFineOffset }, + SPDIndexTCKMax: { getVal: encodeTCKMax }, + SPDIndexTCKMaxFineOffset: { getVal: encodeTCKMaxFineOffset }, + SPDIndexCASFirstByte: { getVal: encodeCASFirstByte }, + SPDIndexCASSecondByte: { getVal: encodeCASSecondByte }, + SPDIndexCASThirdByte: { getVal: encodeCASThirdByte }, + SPDIndexCASFourthByte: { getVal: encodeCASFourthByte }, + SPDIndexTAAMin: { getVal: encodeTAAMin }, + SPDIndexTAAMinFineOffset: { getVal: encodeTAAMinFineOffset }, + SPDIndexTRCDMin: { getVal: encodeTRCDMin }, + SPDIndexTRCDMinFineOffset: { getVal: encodeTRCDMinFineOffset }, + SPDIndexTRPMin: { getVal: encodeTRPMin }, + SPDIndexTRPMinFineOffset: { getVal: encodeTRPMinFineOffset }, + SPDIndexTRASRCMinMSNs: { getVal: encodeTRASRCMinMSNs }, + SPDIndexTRASMinLsb: { getVal: encodeTRASMinLsb }, + SPDIndexTRCMinLsb: { getVal: encodeTRCMinLsb }, + SPDIndexTRCMinFineOffset: { getVal: encodeTRCMinFineOffset }, + SPDIndexTRFC1MinLsb: { getVal: encodeTRFC1MinLsb }, + SPDIndexTRFC1MinMsb: { getVal: encodeTRFC1MinMsb }, + SPDIndexTRFC2MinLsb: { getVal: encodeTRFC2MinLsb }, + SPDIndexTRFC2MinMsb: { getVal: encodeTRFC2MinMsb }, + SPDIndexTRFC4MinLsb: { getVal: encodeTRFC4MinLsb }, + SPDIndexTRFC4MinMsb: { getVal: encodeTRFC4MinMsb }, + SPDIndexTFAWMinMSN: { getVal: encodeTFAWMinMSN }, + SPDIndexTFAWMinLsb: { getVal: encodeTFAWMinLsb }, + SPDIndexTRRDSMin: { getVal: encodeTRRDSMin }, + SPDIndexTRRDSMinFineOffset: { getVal: encodeTRRDSMinFineOffset }, + SPDIndexTRRDLMin: { getVal: encodeTRRDLMin }, + SPDIndexTRRDLMinFineOffset: { getVal: encodeTRRDLMinFineOffset }, + SPDIndexTCCDLMin: { getVal: encodeTCCDLMin }, + SPDIndexTCCDLMinFineOffset: { getVal: encodeTCCDLMinFineOffset }, + SPDIndexTWRMinMSN: { getVal: encodeTWRMinMSN }, + SPDIndexTWRMinLsb: { getVal: encodeTWRMinLsb }, + SPDIndexTWTRMinMSNs: { getVal: encodeTWTRMinMSNs }, + SPDIndexWTRSMinLsb: { getVal: encodeTWTRSMinLsb }, + SPDIndexWTRLMinLsb: { getVal: encodeTWTRLMinLsb }, +} + +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("ddr4-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 validateSpeedMTps(speedBin int) error { + if _, ok := speedBinToSPDEncoding[speedBin]; ok == false { + return fmt.Errorf("Incorrect speed bin: DDR4-", speedBin) + } + return nil +} + +func validateCapacityPerDie(capacityPerDieGb int) error { + if _, ok := densityGbToSPDEncoding[capacityPerDieGb]; ok == false { + return fmt.Errorf("Incorrect capacity per die: ", capacityPerDieGb) + } + return nil +} + +func validateDiesPerPackage(dieCount int) error { + if dieCount >= 1 && dieCount <= 2 { + return nil + } + return fmt.Errorf("Incorrect dies per package count: ", dieCount) +} + +func validateDeviceBusWidth(width int) error { + if width != 8 && width != 16 { + return fmt.Errorf("Incorrect device bus width: ", width) + } + return nil +} + +func validateRanksPerPackage(ranks int) error { + if ranks >= 1 && ranks <= 2 { + return nil + } + return fmt.Errorf("Incorrect package ranks: ", ranks) +} + + +func validateCASLatency(CL int) error { + if CL >= 10 && CL <= 24 && CL != 23 { + return nil + } + return fmt.Errorf("Incorrect CAS latency: ", CL) +} + +/* +1) validate memory parts +2) remove any fields that Intel does not care about +*/ + +/* verify the supplied CAS Latencies supported does not match default */ +func verifySupportedCASLatencies(part *memPart) error { + if part.Attribs.CASLatencies == getDefaultCASLatencies(&part.Attribs) { + return fmt.Errorf("CASLatencies for %s already matches default,\nPlease remove CASLatencies override line from the %s part attributes in the global part list and regenerate SPD Manifest", part.Name, part.Name) + } + + return nil +} + +func validateMemoryParts(memParts *memParts) error { + for i := 0; i < len(memParts.MemParts); i++ { + if err := validateSpeedMTps(memParts.MemParts[i].Attribs.SpeedMTps); err != nil { + return err + } + if err := validateCapacityPerDie(memParts.MemParts[i].Attribs.CapacityPerDieGb); err != nil { + return err + } + if err := validateDiesPerPackage(memParts.MemParts[i].Attribs.DiesPerPackage); err != nil { + return err + } + if err := validateDeviceBusWidth(memParts.MemParts[i].Attribs.DeviceBusWidth); err != nil { + return err + } + if err := validateRanksPerPackage(memParts.MemParts[i].Attribs.RanksPerPackage); err != nil { + return err + } + if err := validateCASLatency(memParts.MemParts[i].Attribs.CL_nRCD_nRP); err != nil { + return err + } + /* If CAS Latency was supplied, make sure it doesn't match default value */ + if len(memParts.MemParts[i].Attribs.CASLatencies) != 0 { + if err := verifySupportedCASLatencies(&memParts.MemParts[i]); err != nil { + return err + } + } + } + + return nil +} + +const ( + /* First Byte */ + CAS9 = 1 << 2 + CAS10 = 1 << 3 + CAS11 = 1 << 4 + CAS12 = 1 << 5 + CAS13 = 1 << 6 + CAS14 = 1 << 7 + /* Second Byte */ + CAS15 = 1 << 0 + CAS16 = 1 << 1 + CAS17 = 1 << 2 + CAS18 = 1 << 3 + CAS19 = 1 << 4 + CAS20 = 1 << 5 + CAS21 = 1 << 6 + CAS22 = 1 << 7 + /* Third Byte */ + CAS24 = 1 << 1 +) + +func encodeLatencies(latency int, memAttribs *memAttributes) error { + switch latency { + case 9: + memAttribs.CASFirstByte |= CAS9 + case 10: + memAttribs.CASFirstByte |= CAS10 + case 11: + memAttribs.CASFirstByte |= CAS11 + case 12: + memAttribs.CASFirstByte |= CAS12 + case 13: + memAttribs.CASFirstByte |= CAS13 + case 14: + memAttribs.CASFirstByte |= CAS14 + case 15: + memAttribs.CASSecondByte |= CAS15 + case 16: + memAttribs.CASSecondByte |= CAS16 + case 17: + memAttribs.CASSecondByte |= CAS17 + case 18: + memAttribs.CASSecondByte |= CAS18 + case 19: + memAttribs.CASSecondByte |= CAS19 + case 20: + memAttribs.CASSecondByte |= CAS20 + case 21: + memAttribs.CASSecondByte |= CAS21 + case 22: + memAttribs.CASSecondByte |= CAS22 + case 24: + memAttribs.CASThirdByte |= CAS24 + default: + fmt.Errorf("Incorrect CAS Latency: ", latency) + } + + return nil +} + +/* Default CAS Latencies from Speed Bin tables in JEDS79-4C */ +func getDefaultCASLatencies(memAttribs *memAttributes) string { + var str string + + switch memAttribs.SpeedMTps { + case 1600: + switch memAttribs.CL_nRCD_nRP { + case 10: + str = "9 10 11 12" + case 11: + str = "9 11 12" + case 12: + str = "10 12" + } + case 1866: + switch memAttribs.CL_nRCD_nRP { + case 12: + str = "9 10 12 13 14" + case 13: + str = "9 11 12 13 14" + case 14: + str = "10 12 14" + } + case 2133: + switch memAttribs.CL_nRCD_nRP { + case 14: + str = "9 10 12 14 15 16" + case 15: + str = "9 11 12 13 14 15 16" + case 16: + str = "10 12 14 16" + } + case 2400: + switch memAttribs.CL_nRCD_nRP { + case 15: + str = "9 10 12 14 15 16 17 18" + case 16: + str = "9 11 12 13 14 15 16 17 18" + case 17: + str = "10 11 12 13 14 15 16 17 18" + case 18: + str = "10 12 14 16 18" + } + case 2666: + switch memAttribs.CL_nRCD_nRP { + case 17: + str = "9 10 11 12 13 14 15 16 17 18 19 20" + case 18: + str = "9 10 11 12 13 14 15 16 17 18 19 20" + case 19: + str = "10 11 12 13 14 15 16 17 18 19 20" + case 20: + str = "10 12 14 16 18 20" + } + case 2933: + switch memAttribs.CL_nRCD_nRP { + case 19: + str = "9 10 11 12 13 14 15 16 17 18 19 20 21 22" + case 20: + str = "10 11 12 13 14 15 16 17 18 19 20 21 22" + case 21: + str = "10 11 12 13 14 15 16 17 18 19 20 21 22" + case 22: + str = "10 12 14 16 18 20 22" + } + case 3200: + switch memAttribs.CL_nRCD_nRP { + case 20: + str = "9 10 11 12 13 14 15 16 17 18 19 20 21 22 24" + case 22: + str = "10 11 12 13 14 15 16 17 18 19 20 21 22 24" + case 24: + str = "10 12 14 16 18 20 22 24" + } + } + + return str +} + +func updateCAS(memAttribs *memAttributes) error { + if len(memAttribs.CASLatencies) == 0 { + memAttribs.CASLatencies = getDefaultCASLatencies(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 getTAAMinPs(memAttribs *memAttributes) int { + return (memAttribs.CL_nRCD_nRP * 2000000) / memAttribs.SpeedMTps +} + +func updateTAAMin(memAttribs *memAttributes) { + if memAttribs.TAAMinPs == 0 { + memAttribs.TAAMinPs = getTAAMinPs(memAttribs) + } +} + +func updateTRCDMin(memAttribs *memAttributes) { + /* tRCDmin is same as tAAmin for all cases */ + if memAttribs.TRCDMinPs == 0 { + memAttribs.TRCDMinPs = getTAAMinPs(memAttribs) + } +} + +func updateTRPMin(memAttribs *memAttributes) { + /* tRPmin is same as tAAmin for all cases */ + if memAttribs.TRPMinPs == 0 { + memAttribs.TRPMinPs = getTAAMinPs(memAttribs) + } +} + +func updateTRASMin(memAttribs *memAttributes) { + if memAttribs.TRASMinPs == 0 { + memAttribs.TRASMinPs = speedBinToSPDEncoding[memAttribs.SpeedMTps].TRASMinPs + } +} + + +func updateTRCMin(memAttribs *memAttributes) { + if memAttribs.TRCMinPs == 0 { + memAttribs.TRCMinPs = getTRCMinPs(memAttribs) + } +} + +func updateTCK(memAttribs *memAttributes) { + if memAttribs.TCKMinPs == 0 { + memAttribs.TCKMinPs = getDefaultTCKMinPs(memAttribs) + } + if memAttribs.TCKMaxPs == 0 { + memAttribs.TCKMaxPs = speedBinToSPDEncoding[memAttribs.SpeedMTps].TCKMaxPs + } +} + +func updateTWRMin(memAttribs *memAttributes) { + if memAttribs.TWRMinPs == 0 { + memAttribs.TWRMinPs = TimingValueTWRMinPs + } +} + +func updateTWTRMin(memAttribs *memAttributes) { + if memAttribs.TWTRLMinPs == 0 { + memAttribs.TWTRLMinPs = TimingValueTWTRLMinPs + } + if memAttribs.TWTRSMinPs == 0 { + memAttribs.TWTRSMinPs = TimingValueTWTRSMinPs + } +} + +func updateMemoryAttributes(memAttribs *memAttributes) { + updateTCK(memAttribs) + updateTAAMin(memAttribs) + updateTRCDMin(memAttribs) + updateTRPMin(memAttribs) + updateTRASMin(memAttribs) + updateTRCMin(memAttribs) + updateTWRMin(memAttribs) + updateTWTRMin(memAttribs) + updateCAS(memAttribs) + updateTRFC1Min(memAttribs) + updateTRFC2Min(memAttribs) + updateTRFC4Min(memAttribs) + updateTCCDLMin(memAttribs) + updateTRRDSMin(memAttribs) + updateTRRDLMin(memAttribs) + updateTFAWMin(memAttribs) +} + +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\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++ { + updateMemoryAttributes(&memParts.MemParts[i].Attribs) + 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) + } +} diff --git a/util/spd_tools/ddr4/global_ddr4_mem_parts.json.txt b/util/spd_tools/ddr4/global_ddr4_mem_parts.json.txt new file mode 100644 index 0000000000..4e9c7c8e95 --- /dev/null +++ b/util/spd_tools/ddr4/global_ddr4_mem_parts.json.txt @@ -0,0 +1,37 @@ +{ + "parts": [ + { + "name": "H5AN8G6NDJR-XNC", + "attribs": { + "speedMTps": 3200, + "CL_nRCD_nRP": 22, + "capacityPerDieGb": 8, + "diesPerPackage": 1, + "deviceBusWidth": 16, + "ranksPerPackage": 1 + } + }, + { + "name": "MT40A512M16TB-062E:J", + "attribs": { + "speedMTps": 3200, + "CL_nRCD_nRP": 22, + "capacityPerDieGb": 8, + "diesPerPackage": 1, + "deviceBusWidth": 16, + "ranksPerPackage": 1 + } + }, + { + "name": "H5ANAG6NCMR-XNC", + "attribs": { + "speedMTps": 3200, + "CL_nRCD_nRP": 22, + "capacityPerDieGb": 8, + "diesPerPackage": 2, + "deviceBusWidth": 16, + "ranksPerPackage": 1 + } + } + ] +} |