diff --git a/Makefile b/Makefile index fc8dd9d..1a5a532 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ CONFIG := $(TOP_DIR)/example_cfgs/freepdk45.cfg OUT_DIR := $(TOP_DIR)/results run: - ./scripts/run.py $(CONFIG) --output_dir $(OUT_DIR) + PYTHONDONTWRITEBYTECODE=1 ./scripts/run.py $(CONFIG) --output_dir $(OUT_DIR) view.%: klayout ./$(OUT_DIR)/$*/$*.lef & @@ -32,3 +32,5 @@ $(CACTI_BUILD_DIR): clean_tools: rm -rf $(CACTI_BUILD_DIR) +test: + bash ./scripts/tests/run_test.sh \ No newline at end of file diff --git a/README.md b/README.md index f9972f1..4ddf759 100644 --- a/README.md +++ b/README.md @@ -15,30 +15,87 @@ $ make tools ## Usage -### Configuration File +### Process Configuration The input to the BSG Black-box SRAM generator is a simple JSON file that contains some information about the technology node you are targeting as well as the size and names of SRAMs you would like to generate. Below is an example -JSON file that can be found in `./example_cfgs/freepdk45.cfg`: +JSON file that can be found in `./example_cfgs/asap7.cfg`: ``` { - "tech_nm": 45, - "voltage": 1.1, - "metalPrefix": "metal", - "pinWidth_nm": 70, - "pinPitch_nm": 140, - "snapWidth_nm": 190, - "snapHeight_nm": 1400, - "flipPins": True, + "tech_nm": 7, + "voltage": 0.7, + "metalPrefix": "M", + "manufacturing_grid_nm": 1, + "pinParams": { + "x_metLayerPin": 3, + "x_pinPitch_nm": 36, + "x_pinWidth_nm": 18, + "x_pinHeight_nm": 36, + "x_pinOffset_nm": 0, + "y_metLayerPin": 4, + "y_pinPitch_nm": 48, + "y_pinWidth_nm": 24, + "y_pinHeight_nm": 48, + "y_pinOffset_nm": 0 + }, + "powerGridParams": { + "directionPowerGrid": "horizontal", + "metLayerPowerGrid": 4, + "powerGridWidth_nm": 96, + "powerGridPitch_nm": 384 + }, + "timing": { + "t_setup_ns": 0.050, + "t_hold_ns": 0.050, + "cap_input_pf": 0.005 + }, + "additionalParams": { + "heightSnapPinPitch": false, + "widthSnapPinPitch": true, + "column_mux_factor": 4, + "snapWidth_nm": 190, + "snapHeight_nm": 1400 + }, + "use_custom_tech": true, + "custom_tech": { + "access_time_ns": 0.2183, + "cycle_time_ns": 0.2566, + "fo4_ps": 9.0632, + "standby_leakage_per_bank_mW": 0.1289, + "pin_dynamic_power_mW": 0.0013449, + "finPitch_nm": 27, + "contacted_poly_pitch_nm": 54, + "h0_tracks": 10, + "w0_polys": 2, + "dh_read": 2, + "dw_read": 0.5, + "dh_write": 2.5, + "dw_write": 0.5, + "dh_rw": 1, + "dw_rw": 0.5 + }, + "add_fakeram_extension": true, "srams": [ - {"name": "sram_32x32_1rw", "width": 32, "depth": 32, "banks": 1}, - {"name": "sram_8x512_1rw", "width": 8, "depth": 512, "banks": 1} + { + "name": "testram7_1rw_32w1024d_sram", + "width": 32, + "depth": 1024, + "banks": 1, + "column_mux_factor": 6, + "write_mode": "write_first", + "ports": { + "r": 0, + "w": 0, + "rw": 1 + } + }, + ... ] } ``` - +----- `tech_nm` - The name of the target technology node (in nm). Used in Cacti for modeling PPA of the SRAM. @@ -46,27 +103,148 @@ modeling PPA of the SRAM. `metalPrefix` - The string that prefixes metal layers. -`pinWidth_nm` - The width of the signal pins (in nm). +`manufacturing_grid_nm` - (Optional : Default 1) The manufacturing grid for specific technology (in nm). + +----- +`pinParams`: Main Pin Parameters + +`x_metLayerPin` - Metal layer for horizontal pins (top and bottom of SRAM) + +`y_metLayerPin` - Metal layer for vertical pins (left and right of SRAM) + +`x_pinPitch_nm` - The minimum pin pitch for signal pins (in nm) for top/bottom (horizontal) pins. + +`y_pinPitch_nm` - The minimum pin pitch for signal pins (in nm) for left/right (vertical) pins. + +`x_pinOffset_nm` - (Optional : Default 0) Fixed offset x pin pitch (in nm), x pin pitch + x offset. Unaffected by pinPitchFactor + +`y_pinOffset_nm` - (Optional : Default 0) Fixed offset y pin pitch (in nm), y pin pitch + y offset. Unaffected by pinPitchFactor + +`x_pinWidth_nm` - The width of the horizontal signal pins (in nm). + +`x_pinHeight_nm` - The height of the horizontal signal pins (in nm). + +`y_pinWidth_nm` - The width of the vertical signal pins (in nm). + +`y_pinHeight_nm` - The height of the vertical signal pins (in nm). + +----- +`powerGridParams`: Main Power Grid Parameters + +`directionPowerGrid` - (Options: "vertical" or "horizontal") Specify the direction of strapes. -`pinPitch_nm` - The minimum pin pitch for signal pins (in nm). All pins will -have a pitch that is a multuple of this pitch. The first pin will be a -multiple of this pitch from the bottom edge of the macro too. +`metLayerPowerGrid` - (Optional : Default 4) Metal layer of of strapes. -`snapWidth_nm` - (Optional : 1) Snap the width of the generated memory to a +`powerGridWidth_nm` - (Optional : Default "pinWidth_nm") Strapes width (in nm). + +`powerGridPitch_nm` - (Optional : Default "y_pinPitch_nm") Strapes pitch (in nm). + +`powerGridOffset_nm` - (Optional : Default 0) Strapes offset (in nm), pitch + offset. + +----- +`timing`: Timing for all SRAMs + +`t_setup_ns` - Arbitrary hold time (in ns). + +`t_hold_ns` - Arbitrary setup time (in ns). + +`cap_input_pf` - Capacitance input (in pf). + +----- +`additionalParams`: Optional Additional Parameters for SRAMs + +`heightSnapPinPitch` - (Optional : Default False) Snap SRAMs height to nearest pin pitch. Y pin offset will be ignored. + +`widthSnapPinPitch` - (Optional : Default False) Snap SRAMs width to nearest pin pitch. X pin offset will be ignored. + +`verticalPinsOnly` - (Optional : Default False) Set all pins to left and right sides of sram. + +`snapWidth_nm` - (Optional : Default 1) Snap the width of the generated memory to a multiple of the given value. -`snapHeight_nm` - (Optional : 1) Snap the height of the generated memory to a +`snapHeight_nm` - (Optional : Default 1) Snap the height of the generated memory to a multiple of the given value. -`flipPins` - (Optional : false) Flip the pins. If set to false then metal 1 is -assumed to be vertical. This means that signal pins will be on metal 4 and the -supply straps (also on metal 4) will be horizontal. If set to true then metal 1 -is assumed to be horizontal. This means that signal pins will be on metal 3 and -the supply straps (on metal 4) will be vertical. +`column_mux_factor` - (Optional : Default 1) It reduces the number of sense amplifiers needed, saving area, but may increase access time. When used the height is divided by its column mux factor and width multiplied by its column mux factor for all srams. Column mux factor defaults to 1. Can be overriden for a specific sram with parameter column_mux_factor_override in "sram". If CACTI is ran, this is ignored. + +`add_fakeram_extension` - (Optional : Default False) Add 'fakeram.' prefix to all SRAM names. + +### Custom Tech Configuration + +Setting "use_custom_tech" and defining the parameters in "custom_tech" in json would be optimal since cacti does not support below 28nm or above 180nm. This config can be found in `example_cfgs/asap7.cfg` + +`hybrid` - (Optional : Default False) Overrides specific cacti values with values in yml file. Otherwise will use cacti as default. + +`access_time_ns` - Access time (in ns). + +`cycle_time_ns` - Cycle time (in ns). + +`fo4_ps` - Fanout of 4 (in ps). + +`standby_leakage_per_bank_mW` - Standby leakage per bank (in mW). + +`pin_dynamic_power_mW` - All pin dynamic power (in mW). + +`finPitch_nm` - (FinFET Architecture) Fin pitch (in nm). + +`contacted_poly_pitch_nm` - (FinFET Architecture) Contacted poly pitch (in nm). + +`h0_tracks` - (FinFET Architecture) Height Tracks. + +`w0_polys` - (FinFET Architecture) Width Polys. + +`dh_read` - (Optional : Default 1) Dummy read port height overhead scaling factor. + +`dw_read` - (Optional : Default 1) Dummy read port width overhead scaling factor. + +`dh_write` - (Optional : Default 1) Dummy write port height overhead scaling factor. + +`dw_write` - (Optional : Default 1) Dummy write port width overhead scaling factor. + +`dh_rw` - (Optional : Default 1) Dummy read write port height overhead scaling factor. + +`dw_rw` - (Optional : Default 1) Dummy read write port width overhead scaling factor. + + +``` +"access_time_ns": 0.2183, +"cycle_time_ns": 0.2566, +"fo4_ps": 9.0632, +"standby_leakage_per_bank_mW": 0.1289, +"pin_dynamic_power_mW": 0.0013449, + +"finPitch_nm": 27, +"contacted_poly_pitch_nm": 54, + +"h0_tracks": 10, +"w0_polys": 2, + +// Optional params, default: 1 +"dh_read": 2, +"dw_read": 0.5, +"dh_write": 2.5, +"dw_write": 0.5, +"dh_rw": 1, +"dw_rw": 0.5 + +``` + +### Memory Configuration `srams` - A list of SRAMs to generate. Each sram should have a `name`, `width` (or the number of bits per word), `depth` (or number of words), and `banks`. +`banks` - (Optional : Default 1 | Options "2" , "4") Specify number of banks. + +`column_mux_factor` - (Optional : Default "column_mux_factor") Overrides column_mux_factor for a specific sram. + +`write_mode` - (Optional : Default "write_first" | Options "read_first" , "write_first") For Read Write ports, optional to chose as read_first otherwise write_first. + +`write_granularity` - (Optional : Default "width") Specifies number of bits that can be written in a single write operation. + +`Ports` : + - Read ports, address and control pins are on the left, data pins are on the top + - Write ports, address and control pins are on the right, data pins are on the bottom. ### Running the Generator diff --git a/example_cfgs/asap7.cfg b/example_cfgs/asap7.cfg index 2cc3e41..6fea715 100644 --- a/example_cfgs/asap7.cfg +++ b/example_cfgs/asap7.cfg @@ -1,48 +1,169 @@ -# ABKGroup's FakeRAM2.0 config, added to allow dynamic RAM generation of asap7 tech - { - # The process node. + "tech_nm": 7, - # The operating voltage. "voltage": 0.7, - # String to add in front of every metal layer number for the layer name. "metalPrefix": "M", - - # The pin width for signal pins. - "pinWidth_nm": 24, - # The minimum pin pitch for signal pins - "pinPitch_nm": 48, + "manufacturing_grid_nm": 1, - # Metal track pitch - "metal_track_pitch_nm": 48, + # Y pins are on 'left' and 'right' sides + # X pins are on 'top' and 'bottom' sides + "pinParams": { + # set 36nm to x track pitch in met3 + "x_metLayerPin": 3, + "x_pinPitch_nm": 36, + "x_pinWidth_nm" : 18, + "x_pinHeight_nm": 36, + "x_pinOffset_nm": 0, - # Manufacturing Grid - "manufacturing_grid_nm": 1, + # set 48nm to y track pitch in met4 + "y_metLayerPin": 4, + "y_pinPitch_nm": 48, + "y_pinWidth_nm" : 24, + "y_pinHeight_nm": 48, + "y_pinOffset_nm": 0 + }, + + # Power Grid + "powerGridParams": { + # "vertical" or "horizontal" strapes + "directionPowerGrid": "horizontal", + "metLayerPowerGrid": 4, + + # pinWidth * 4 = 96nm + "powerGridWidth_nm": 96, + + # met4 x track pitch * 8 = 384nm + "powerGridPitch_nm": 384 + }, + + # timing for all srams + "timing": { + "t_setup_ns": 0.050, + "t_hold_ns": 0.050, + "cap_input_pf": 0.005 + }, + + "additionalParams": { + # snap sram dimensions to track + "heightSnapPinPitch": false, + "widthSnapPinPitch": true, + + # column mux factor to all srams unless overriden + "column_mux_factor": 4, + "snapWidth_nm": 190, + "snapHeight_nm": 1400 + }, + + # uses custom_tech, defaults to false + "use_custom_tech": true, - # Contacted Poly Pitch - "contacted_poly_pitch_nm": 54, + # Custom tech parameters + "custom_tech": { + "access_time_ns": 0.2183, + "cycle_time_ns": 0.2566, + "fo4_ps": 9.0632, + "standby_leakage_per_bank_mW": 0.1289, + "pin_dynamic_power_mW": 0.0013449, - #column mux factor - "column_mux_factor": 1, + "finPitch_nm": 27, + "contacted_poly_pitch_nm": 54, + + # Needed tracks/poly for asap7 122 sram cell + # 10 fin pitches + "h0_tracks": 10, + "w0_polys": 2, - # Fin pitch - "fin_pitch_nm" : 27, + # Optional dummy height/width port overhead values + "dh_read": 2, + "dw_read": 0.5, + "dh_write": 2.5, + "dw_write": 0.5, + "dh_rw": 1, + "dw_rw": 0.5 - # Flip Pins = false for M4, true for M3 - "flipPins": false, + }, - # Optional snap the width and height of the sram to a multiple value. - "snap_width_nm": 190, - "snap_height_nm": 1400, + # add 'fakeram.' infront of all names + # for openroad flow scripts use: + # export GDS_ALLOW_EMPTY=fakeram.* + "add_fakeram_extension": true, - # List of SRAM configurations (name width depth and banks) "srams": [ - {"name": "fakeram7_64x21", "width": 21, "depth": 64, "banks": 2, "ports": "1rw", "port_clks": "[0], [], []"}, - {"name": "fakeram7_256x34", "width": 34, "depth": 256, "banks": 2, "ports": "1rw", "port_clks": "[0], [], []"}, - {"name": "fakeram7_2048x39", "width": 39, "depth": 2048, "banks": 4, "ports": "1rw", "port_clks": "[0], [], []"} - ] + + # Basic single-port RW SRAM, medium size + {"name": "testram7_1rw_32w1024d_sram", + "width": 32, + "depth": 1024, + "banks": 1, + "column_mux_factor": 6, + "write_mode": "write_first", + "ports": { + "r": 0, + "w": 0, + "rw": 1 + } + }, + + # Dual-port SRAM (1R+1W), small size with write granularity + {"name": "testram7_1r1w_16w256d_8_sram", + "width": 16, + "depth": 256, + "banks": 1, + "column_mux_factor": 4, + "write_mode": "read_first", + "write_granularity": 8, + "ports": { + "r": 1, + "w": 1, + "rw": 0 + } + }, + + # Mixed ports (1RW+1R), dual bank configuration + {"name": "testram7_1rw1r_64w512d2b_sram", + "width": 64, + "depth": 512, + "banks": 2, + "write_mode": "write_first", + "ports": { + "r": 1, + "w": 0, + "rw": 1 + } + }, + + # Write-only SRAM with fine-grain write capability + {"name": "testram7_1w_48w1024d_4wm_sram", + "width": 48, + "depth": 1024, + "banks": 1, + "column_mux_factor": 3, + "write_mode": "write_first", + "write_granularity": 4, + "ports": { + "r": 0, + "w": 1, + "rw": 0 + } + }, + + # Complex multi-port (2R+2W), large size, dual bank + {"name": "testram7_2r2w_80w2048d2b_16wm_sram", + "width": 80, + "depth": 2048, + "banks": 2, + "write_mode": "read_first", + "column_mux_factor": 4, + "write_granularity": 16, + "ports": { + "r": 2, + "w": 2, + "rw": 0 + } + } + ] } \ No newline at end of file diff --git a/example_cfgs/freepdk45.cfg b/example_cfgs/freepdk45.cfg index 099a9bf..63c8bca 100755 --- a/example_cfgs/freepdk45.cfg +++ b/example_cfgs/freepdk45.cfg @@ -1,58 +1,150 @@ { - # The process node. This is used to tell cacti what technology to use when - # estimating power, performance and area numbers. "tech_nm": 45, - # The operating voltage. "voltage": 1.1, - # String to add in front of every metal layer number for the layer name. "metalPrefix": "metal", + + "manufacturing_grid_nm": 5, - # The pin width for signal pins. - "pinWidth_nm": 70, - - # The minimum pin pitch for signal pins (all pins will have a pitch that is a - # multuple of this pitch. The first pin will be a multiple of this pitch from - # the bottom edge of the macro too. - "pinPitch_nm": 140, - - # Optional: snap the width and height of the sram to a multiple value. - "snapWidth_nm": 190, - "snapHeight_nm": 1400, - - # Flips the pin orientations. Non-fliped assumes metal1 is vertical therefore - # supply pins on metal4 will be horizontal and signal pins will also be on - # metal4. If set to true, supply pins on metal4 will be vertical and signal - # pins will be on metal3. - "flipPins": true, - - # List of SRAM configurations (name, width, depth, and banks) - "srams": [ - { - "name": "fakeram_32x384_1rw1r", - "width": 32, - "depth": 384, - "banks": 1, - "write_mode": "write_first", - "ports": "1rw1r", - "port_clks" : "[0], [1], []" +# Y pins are on 'left' and 'right' sides +# X pins are on 'top' and 'bottom' sides + "pinParams": { + # vertical pin to align met3 140 nm y track pitch + "y_metLayerPin": 3, + "y_pinPitch_nm": 140, + "y_pinWidth_nm": 70, + "y_pinHeight_nm": 70, + "y_pinOffset_nm": 0, + + # horizontal pin to align met2 190nm x track pitch + "x_metLayerPin": 2, + "x_pinPitch_nm": 190, + "x_pinWidth_nm": 70, + "x_pinHeight_nm": 70, + "x_pinOffset_nm": 0 + }, + + # Power Grid + "powerGridParams": { + # "vertical" or "horizontal" strapes + "directionPowerGrid": "vertical", + "metLayerPowerGrid": 4, + + # pinWidth * 4 = 280nm + "powerGridWidth_nm": 280, + + # met4 x track pitch * 8 = 1520nm + "powerGridPitch_nm": 1520 + }, + + # timing for all srams + "timing": { + "t_setup_ns": 0.050, + "t_hold_ns": 0.050, + "cap_input_pf": 0.005 + }, + + "additionalParams": { + # snap sram dimensions to track (reccomended) + # y offset ignored + "heightSnapPinPitch": true, + + # x offset ignored + "widthSnapPinPitch": true, + + # column mux factor to all srams unless overriden + "snapWidth_nm": 190, + "snapHeight_nm": 1400 + }, + + # add 'fakeram.' infront of all names + # for openroad flow scripts use: + # export GDS_ALLOW_EMPTY=fakeram.* + "add_fakeram_extension": true, + + # List of SRAM configurations (name width depth and banks) + "srams": [ + # Basic single-port RW SRAM, medium size + {"name": "testram45_1rw_32w1024d_sram", + "width": 32, + "depth": 1024, + "banks": 1, + "write_mode": "write_first", + "ports": { + "r": 0, + "w": 0, + "rw": 1 + } + }, + + # Dual-port SRAM (1R+1W), small size with write granularity + {"name": "testram45_1r1w_16w256d_8_sram", + "width": 16, + "depth": 256, + "banks": 1, + "write_mode": "read_first", + "write_granularity": 8, + "ports": { + "r": 1, + "w": 1, + "rw": 0 + } }, - { - "name": "fakeram_20x64_2r1w", - "width": 20, - "depth": 64, - "banks": 1, - "ports": "2r1w", - "port_clks" : "[], [1], [1]" + + # Mixed ports (1RW+1R), dual bank configuration + {"name": "testram45_1rw1r_64w512d2b_sram", + "width": 64, + "depth": 512, + "banks": 2, + "write_mode": "write_first", + "ports": { + "r": 1, + "w": 0, + "rw": 1 + } + }, + + # Write-only SRAM with fine-grain write capability + {"name": "testram45_1w_48w1024d_4wm_sram", + "width": 48, + "depth": 1024, + "banks": 1, + "write_mode": "write_first", + "write_granularity": 4, + "ports": { + "r": 0, + "w": 1, + "rw": 0 + } + }, + + # Mixed Multi port (2W + 2RW) with large write granularity, + {"name": "testram45_2w2rw_128d2048w_16wm_sram", + "width": 128, + "depth": 2048, + "banks": 2, + "write_mode": "write_first", + "write_granularity": 16, + "ports": { + "r": 0, + "w": 2, + "rw": 2 + } }, - { - "name": "fakeram_512x1024_1r1w", - "width": 512, - "depth": 1024, - "banks": 1, - "ports": "1r1w", - "port_clks" : "[], [1], [1]" + + # Complex multi-port (2R+2W), large size, dual bank + {"name": "testram45_2r2w_80w2048d2b_16wm_sram", + "width": 80, + "depth": 2048, + "banks": 2, + "write_mode": "read_first", + "write_granularity": 16, + "ports": { + "r": 2, + "w": 2, + "rw": 0 + } } ] } \ No newline at end of file diff --git a/example_cfgs/sky130.cfg b/example_cfgs/sky130.cfg index e52085c..df26faa 100644 --- a/example_cfgs/sky130.cfg +++ b/example_cfgs/sky130.cfg @@ -1,61 +1,172 @@ { - # The process node. This is used to tell cacti what technology to use when - # estimating power, performance and area numbers. "tech_nm": 130, - # The operating voltage. "voltage": 1.8, - # String to add in front of every metal layer number for the layer name. "metalPrefix": "met", + + "manufacturing_grid_nm": 5, - # The pin width for signal pins. - "pinWidth_nm": 300, +# Y pins are on 'left' and 'right' sides +# X pins are on 'top' and 'bottom' sides + "pinParams": { + # horizontal pin to align met2 460nm x track pitch + "x_metLayerPin": 2, + "x_pinPitch_nm": 460, + "x_pinWidth_nm": 300, + "x_pinHeight_nm": 800, + "x_pinOffset_nm": 0, - # The pin length for the signal pins. - "pinHeight_nm": 800, + # vertical pin to align met3 680 nm y track pitch + "y_metLayerPin": 3, + "y_pinPitch_nm": 680, + "y_pinWidth_nm": 300, + "y_pinHeight_nm": 800, + "y_pinOffset_nm": 0 + }, - # The minimum pin pitch for signal pins (all pins will have a pitch that is a - # multuple of this pitch. The first pin will be a multiple of this pitch from - # the bottom edge of the macro too. - "pinPitch_nm": 600, + # Power Grid + "powerGridParams": { + "metLayerPowerGrid": 4, + # "vertical" or "horizontal" strapes + "directionPowerGrid": "vertical", - # Optional: snap the width and height of the sram to a multiple value. - "snapWidth_nm": 460, - "snapHeight_nm": 2720, + # pinWidth * 4 = 1200nm + "powerGridWidth_nm": 1200, - # Flips the pin orientations. Non-fliped assumes metal1 is vertical therefore - # supply pins on metal4 will be horizontal and signal pins will also be on - # metal4. If set to true, supply pins on metal4 will be vertical and signal - # pins will be on metal3. - "flipPins": true, + # x track pitch = 920nm + # met4 x track pitch * 8 = 7360nm + "powerGridPitch_nm": 7360, + + # offset by depends on direction of powergrid + # met4 x track offset + "powerGridOffset_nm": 0 + }, + + # timing for all srams + "timing": { + "t_setup_ns": 0.050, + "t_hold_ns": 0.050, + "cap_input_pf": 0.005 + }, + + "additionalParams": { + # snap sram dimensions to track (reccomended) + # y offset ignored + "heightSnapPinPitch": true, + + # x offset ignored + "widthSnapPinPitch": true, + + # all pins will be placed on left/right sides of sram + "verticalPinsOnly": false, + + # column mux factor to all srams unless overriden + "column_mux_factor": 1, + "snapWidth_nm": 460, + "snapHeight_nm": 2720 + }, + + # add 'fakeram.' infront of all names + # for openroad flow scripts use: + # export GDS_ALLOW_EMPTY=fakeram.* + "add_fakeram_extension": true, + + # List of SRAM configurations (name width depth and banks) + "srams": [ + # Basic single-port RW SRAM, medium size + {"name": "testram130_1rw_32w1024d_sram", + "width": 32, + "depth": 1024, + "banks": 1, + "write_mode": "write_first", + "ports": { + "r": 0, + "w": 0, + "rw": 1 + } + }, - # List of SRAM configurations (name, width, depth, and banks) - "srams": [ { - "name": "fakeram_32x384_1rw1r", - "width": 32, - "depth": 384, + "name": "sky130_sram_1kbyte_1rw1r_8x1024_8", + "width": 8, + "depth": 1024, "banks": 1, "write_mode": "write_first", - "ports": "1rw1r", - "port_clks" : "[0], [1], []" + "ports": { + "r": 1, + "w": 0, + "rw": 1 + } }, - { - "name": "fakeram_20x64_2r1w", - "width": 20, - "depth": 64, - "banks": 1, - "ports": "2r1w", - "port_clks" : "[], [1], [1]" + + # Dual-port SRAM (1R+1W), small size with write granularity + {"name": "testram130_1r1w_16w256d_8_sram", + "width": 16, + "depth": 256, + "banks": 1, + "write_mode": "read_first", + "write_granularity": 8, + "ports": { + "r": 1, + "w": 1, + "rw": 0 + } }, - { - "name": "fakeram_512x1024_1r1w", - "width": 512, - "depth": 1024, - "banks": 1, - "ports": "1r1w", - "port_clks" : "[], [1], [1]" + + # Mixed ports (1RW+1R), dual bank configuration + {"name": "testram130_1rw1r_64w512d2b_sram", + "width": 64, + "depth": 512, + "banks": 2, + "write_mode": "write_first", + "ports": { + "r": 1, + "w": 0, + "rw": 1 + } + }, + + # Write-only SRAM with fine-grain write capability + {"name": "testram130_1w_48w1024d_4wm_sram", + "width": 48, + "depth": 1024, + "banks": 1, + "write_mode": "write_first", + "write_granularity": 4, + "ports": { + "r": 0, + "w": 1, + "rw": 0 + } + }, + + # Mixed Multi port (2W + 2RW) with large write granularity, + {"name": "testram130_2w2rw_128d2048w_16wm_sram", + "width": 128, + "depth": 2048, + "banks": 1, + "write_mode": "write_first", + "write_granularity": 16, + "ports": { + "r": 0, + "w": 2, + "rw": 2 + } + }, + + # Complex multi-port (2R+2W), large size, dual bank + {"name": "testram130_2r2w_80w2048d2b_16wm_sram", + "width": 80, + "depth": 2048, + "banks": 2, + "write_mode": "read_first", + "write_granularity": 16, + "ports": { + "r": 2, + "w": 2, + "rw": 0 + } } ] } diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/run.py b/scripts/run.py index 07b337d..5ca6fcf 100755 --- a/scripts/run.py +++ b/scripts/run.py @@ -5,13 +5,16 @@ import argparse from utils.class_process import Process -from utils.class_memory import Memory +from utils.mem_init.modules import * from utils.generate_lib import generate_lib -from utils.generate_lef import generate_lef +from utils.gen_lef.lef_core import generate_lef from utils.generate_verilog import generate_verilog from utils.generate_verilog import generate_verilog_bb +from utils.mem_init.mem_init import memory_initializer +from utils.mem_init.mem_globals import print_init_sram + ################################################################################ # RUN GENERATOR # @@ -21,6 +24,49 @@ # found in the JSON configuration file. ################################################################################ +def _print_all_srams(memory_config_list: list[object]) -> None: + banner_line = "=" * 100 + print("\n" + banner_line) + print(" " * 40 + "SRAM CONFIG SUMMARY") + print(banner_line) + + fields = [ + "width_in_bits", "depth", "addr_width", "width_in_bytes", + "total_size", "tech_node_um", "t_hold_ns", "t_setup_ns", + "cap_input_pf", "num_banks", "pinPitchFactor", "cache_type", + "write_mode", "column_mux_factor", "r", "w", "rw", + "has_write_mask", "write_granularity", "area_mm2", "height_um", + "width_um", "wmask", "access_time_ns", "cycle_time_ns", + "standby_leakage_per_bank_mW", "fo4_ps", "capacity_bytes", + "associativity", "output_width_bits", "dyn_read_energy_nj", + "dyn_write_energy_nj", "pin_dynamic_power_mW", + "finPitch_nm", "contacted_poly_pitch_nm", "h0_tracks", + "w0_polys", "dh_read", "dw_read", "dh_write", "dw_write", + "dh_rw", "dw_rw" + ] + + mid = len(fields) // 2 + left_fields, right_fields = fields[:mid], fields[mid:] + + for idx, mem in enumerate(memory_config_list, 1): + print(f"\n[SRAM {idx}] {mem.name}") + print("-" * 100) + + for lf, rf in zip(left_fields, right_fields): + left_val = getattr(mem, lf, None) + right_val = getattr(mem, rf, None) + + # Ensure safe string conversion + left_val = "N/A" if left_val is None else str(left_val) + right_val = "N/A" if right_val is None else str(right_val) + + print(f"{lf:30}: {left_val:<20} {rf:30}: {right_val}") + + print("\n" + banner_line) + print(" " * 40 + "END OF RUN") + print(banner_line + "\n") + + def get_args() -> argparse.Namespace: """ Get command line arguments @@ -45,6 +91,7 @@ def get_args() -> argparse.Namespace: return parser.parse_args() +memory_config_list = [] def main ( args : argparse.Namespace): @@ -56,16 +103,31 @@ def main ( args : argparse.Namespace): # Create a process object (shared by all srams) process = Process(json_data) + custom = json_data.get('custom_tech', None) + # Go through each sram and generate the lib, lef and v files for sram_data in json_data['srams']: - memory = Memory(process, sram_data, args.output_dir, args.cacti_dir) + + mem_init = Memory(process, sram_data) + + memory = memory_initializer(mem_init, custom, args.output_dir, args.cacti_dir) + + print_init_sram(memory) + generate_lib(memory) + generate_lef(memory) - generate_verilog(memory, tmChkExpand=process.vlogTimingCheckSignalExpansion) + + generate_verilog(memory) + generate_verilog_bb(memory) + memory_config_list.append(memory) + + ### Entry point if __name__ == '__main__': args = get_args() main( args ) + _print_all_srams(memory_config_list) diff --git a/scripts/utils/__init__.py b/scripts/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/utils/area.py b/scripts/utils/area.py deleted file mode 100644 index d9027b3..0000000 --- a/scripts/utils/area.py +++ /dev/null @@ -1,33 +0,0 @@ -## FakeRAM2.0 (ABKGroup) area calculation for asap7 tech ## -import os -import sys -import math - -def get_macro_dimensions(process, sram_data): - contacted_poly_pitch_um = process.contacted_poly_pitch_nm / 1000 - column_mux_factor = process.column_mux_factor - fin_pitch_um = process.fin_pitch_nm / 1000 - width_in_bits = int(sram_data['width']) - depth = int(sram_data['depth']) - num_banks = int(sram_data['banks']) - - # Corresponds to the recommended 122 cell in asap7 - bitcell_height = 10 * fin_pitch_um - bitcell_width = 2 * contacted_poly_pitch_um - - all_bitcell_height = bitcell_height * depth - all_bitcell_width = bitcell_width * width_in_bits - - if num_banks == 2 or num_banks == 4: - all_bitcell_height = all_bitcell_height / num_banks - all_bitcell_width = all_bitcell_width * num_banks - elif num_banks != 1: - raise Exception("Unsupported number of banks: {}".format(num_banks)) - - all_bitcell_height = all_bitcell_height / column_mux_factor - all_bitcell_width = all_bitcell_width * column_mux_factor - - total_height = all_bitcell_height * 1.2 - total_width = all_bitcell_width * 1.2 - - return total_height, total_width \ No newline at end of file diff --git a/scripts/utils/class_memory.py b/scripts/utils/class_memory.py deleted file mode 100644 index 64bb21b..0000000 --- a/scripts/utils/class_memory.py +++ /dev/null @@ -1,147 +0,0 @@ -import math -import re -import os -import sys -from pathlib import Path -from utils.cacti_config import cacti_config -from utils.area import get_macro_dimensions -################################################################################ -# MEMORY CLASS -# -# This class stores the infromation about a specific memory that is being -# generated. This class takes in a process object, the infromation in one of -# the items in the "sram" list section of the json configuration file, and -# finally runs cacti to generate the rest of the data. -################################################################################ - -class Memory: - - def __init__( self, process, sram_data , output_dir = None, cacti_dir = None): - - self.process = process - self.name = str(sram_data['name']) - self.width_in_bits = int(sram_data['width']) - self.depth = int(sram_data['depth']) - self.num_banks = int(sram_data['banks']) - self.cache_type = str(sram_data['type']) if 'type' in sram_data else 'cache' - - # Port configuration - default to 1RW if not specified - self.port_config = str(sram_data.get('ports', '1rw')) - - # Handle different port configurations - if self.port_config == '1rw1r': - self.rw_ports = 1 - self.r_ports = 1 - self.w_ports = 0 - elif self.port_config == '1r1rw': - self.rw_ports = 1 - self.r_ports = 1 - self.w_ports = 0 - elif self.port_config == '1r1w': - self.rw_ports = 0 - self.r_ports = 1 - self.w_ports = 1 - elif self.port_config == '2r1w': - self.rw_ports = 0 - self.r_ports = 2 - self.w_ports = 1 - elif self.port_config == '1rw1r': - self.rw_ports = 1 - self.r_ports = 1 - self.w_ports = 0 - else: # 1rw - self.rw_ports = 1 - self.r_ports = 0 - self.w_ports = 0 - - # Write granularity (default to bit-level if not specified) - self.write_granularity = int(sram_data.get('write_granularity', 1)) - - # Write mode (default to write-first if not specified) - # Options: 'write_first' (write-through), 'read_first' (no-change), 'write_through' (combinational) - self.write_mode = str(sram_data.get('write_mode', 'write_first')) - - # clk_ct array contains the amount of clks for rw, r, and w ports respectively - self.port_clks = [ list(map(int, re.findall(r'-?\d+', clk_grp))) for clk_grp in re.findall(r'\[[^\]]*\]|(?<=,)\s*(?=,|$)|^\s*(?=,|$)', - sram_data.get('port_clks', '[1], [0], [0]').strip())] - print(self.port_clks) - self.width_in_bytes = math.ceil(self.width_in_bits / 8.0) - self.total_size = self.width_in_bytes * self.depth - if output_dir: # Output dir was set by command line option - p = str(Path(output_dir).expanduser().resolve(strict=False)) - self.results_dir = os.sep.join([p, self.name]) - else: - self.results_dir = os.sep.join([os.getcwd(), 'results', self.name]) - if not os.path.exists( self.results_dir ): - os.makedirs( self.results_dir ) - - if (process.tech_nm == 7): - self.tech_node_nm = 7 - self.associativity = 1 - self.access_time_ns = 0.2183 - self.cycle_time_ns = 0.1566 - self.standby_leakage_per_bank_mW = 0.1289 - self.fo4_ps = 9.0632 - self.height_um, self.width_um = get_macro_dimensions(process, sram_data) - self.pin_dynamic_power_mW = 0.0013449 - else: - if cacti_dir: - self.cacti_dir = cacti_dir - else: - self.cacti_dir = os.environ['CACTI_BUILD_DIR'] - self.__run_cacti() - with open( os.sep.join([self.results_dir, 'cacti.cfg.out']), 'r' ) as fid: - lines = [line for line in fid] - cacti_data = lines[-1].split(',') - self.tech_node_nm = int(cacti_data[0]) - self.capacity_bytes = int(cacti_data[1]) - self.associativity = int(cacti_data[2]) - self.access_time_ns = float(cacti_data[4]) - self.cycle_time_ns = float(cacti_data[5]) - self.pin_dynamic_power_mW = float(cacti_data[8]) - self.standby_leakage_per_bank_mW = float(cacti_data[9]) - self.fo4_ps = float(cacti_data[11]) - self.width_um = float(cacti_data[12]) - self.height_um = float(cacti_data[13]) - - - self.cap_input_pf = 0.005 - - self.tech_node_um = self.tech_node_nm / 1000.0 - - print(f'Original {self.name} size = {self.width_um} x {self.height_um}') - print(f'Port configuration: {self.port_config}') - print(f'Write granularity: {self.write_granularity} bits') - - # Adjust to snap - self.width_um = (math.ceil((self.width_um*1000.0)/self.process.snapWidth_nm)*self.process.snapWidth_nm)/1000.0 - self.height_um = (math.ceil((self.height_um*1000.0)/self.process.snapHeight_nm)*self.process.snapHeight_nm)/1000.0 - self.area_um2 = self.width_um * self.height_um - - #self.pin_dynamic_power_mW = (0.5 * self.cap_input_pf * (float(self.process.voltage)**2))*1e9 ;# P = 0.5*CV^2 - - - self.t_setup_ns = 0.050 ;# arbitrary 50ps setup - self.t_hold_ns = 0.050 ;# arbitrary 50ps hold - - # __run_cacti: shell out to cacti to generate a csv file with more data - # regarding this memory based on the input parameters from the json - # configuration file. - def __run_cacti( self ): - # For different port configurations, configure CACTI appropriately - rw_ports = self.rw_ports - r_ports = self.r_ports if hasattr(self, 'r_ports') else 0 - w_ports = self.w_ports if hasattr(self, 'w_ports') else 0 - - fid = open(os.sep.join([self.results_dir,'cacti.cfg']), 'w') - fid.write( cacti_config.format( self.total_size - , self.width_in_bytes, rw_ports, r_ports, w_ports - , self.process.tech_um, self.width_in_bytes*8, self.num_banks - , self.cache_type )) - fid.close() - odir = os.getcwd() - os.chdir(self.cacti_dir ) - cmd = os.sep.join(['.','cacti -infile ']) + os.sep.join([self.results_dir,'cacti.cfg']) - os.system( cmd) - os.chdir(odir) - diff --git a/scripts/utils/class_process.py b/scripts/utils/class_process.py index bdaecef..d2ff803 100644 --- a/scripts/utils/class_process.py +++ b/scripts/utils/class_process.py @@ -1,3 +1,5 @@ +import sys + ################################################################################ # PROCESS CLASS # @@ -5,37 +7,72 @@ # generated in. Every memory has a pointer to a process object. The information # for the process comes from the json configuration file (typically before the # "sram" list section). -# Additions: -# - Added required information for asap7 (from ABKGroup's FakeRAM2.0) ################################################################################ -class Process: +class Process : + def __init__(self, json_data) : + self.tech_nm = int(json_data['tech_nm']) + self.voltage = str(json_data['voltage']) + self.manufacturing_grid_nm = int(json_data.get('manufacturing_grid_nm', 1)) + self.metalPrefix = str(json_data.get('metalPrefix', None)) + self.metLayerHorizontalPin = int(json_data['pinParams'].get('x_metLayerPin', 4)) + self.x_pinOffset_nm = int(json_data['pinParams'].get('x_pinOffset_nm', 0)) + self.x_pinPitch_nm = int(json_data['pinParams'].get('x_pinPitch_nm', None)) + self.x_pinWidth_nm = int(json_data['pinParams'].get('x_pinWidth_nm')) + self.x_pinHeight_nm = int(json_data['pinParams'].get('x_pinHeight_nm', self.x_pinWidth_nm)) + self.metLayerVerticalPin = int(json_data['pinParams'].get('y_metLayerPin', 4)) + self.y_pinPitch_nm = int(json_data['pinParams'].get('y_pinPitch_nm', None)) + self.y_pinWidth_nm = int(json_data['pinParams'].get('y_pinWidth_nm')) + self.y_pinHeight_nm = int(json_data['pinParams'].get('y_pinHeight_nm', self.y_pinWidth_nm)) + self.y_pinOffset_nm = int(json_data['pinParams'].get('y_pinOffset_nm', 0)) + self.metLayerPowerGrid = int(json_data['powerGridParams'].get('metLayerPowerGrid', 4)) + self.directionPowerGrid = str(json_data['powerGridParams'].get('directionPowerGrid', "vertical")) + local_pinWidth = self.x_pinWidth_nm if self.directionPowerGrid == 'vertical' else self.y_pinWidth_nm + self.powerGridWidth_nm = int(json_data['powerGridParams'].get('powerGridWidth_nm', local_pinWidth)) + self.powerGridPitch_nm = int(json_data['powerGridParams'].get('powerGridPitch_nm', self.y_pinPitch_nm)) + self.powerGridOffset_nm = int(json_data['powerGridParams'].get('powerGridOffset_nm', 0)) + self.heightSnapPinPitch = bool(json_data['additionalParams'].get('heightSnapPinPitch', False)) + self.widthSnapPinPitch = bool(json_data['additionalParams'].get('widthSnapPinPitch', False)) + self.equidistantPins = bool(json_data['additionalParams'].get('equidistantPins', False)) + self.column_mux_factor = int(json_data['additionalParams'].get('column_mux_factor', 1)) + self.snapWidth_nm = int(json_data['additionalParams'].get('snapWidth_nm', 1)) + self.snapHeight_nm = int(json_data['additionalParams'].get('snapHeight_nm', 1)) + self.t_setup_ns = float(json_data['timing'].get('t_setup_ns', 0.050)) + self.t_hold_ns = float(json_data['timing'].get('t_hold_ns', 0.050)) + self.cap_input_pf = float(json_data['timing'].get('cap_input_pf', 0.005)) + self.use_custom_tech = bool(json_data.get('use_custom_tech', False)) + self.add_fakeram_extension = bool(json_data.get('add_fakeram_extension', False)) + + self.pinPitch_nm = self.y_pinPitch_nm + + # Converted values + self.tech_um = self.tech_nm / 1000.0 + self.manufacturing_grid_um = self.manufacturing_grid_nm / 1000.0 + + self.pinPitch_um = self.pinPitch_nm / 1000.0 + self.y_pinPitch_um = self.y_pinPitch_nm / 1000.0 + self.x_pinPitch_um = self.x_pinPitch_nm / 1000.0 + self.y_pinOffset_um = self.y_pinOffset_nm / 1000.0 + self.x_pinOffset_um = self.x_pinOffset_nm / 1000.0 + self.powerGridWidth_um = self.powerGridWidth_nm / 1000.0 + self.powerGridPitch_um = self.powerGridPitch_nm / 1000.0 + self.powerGridOffset_um = self.powerGridOffset_nm / 1000.0 - def __init__(self, json_data): + self.x_pinWidth_um = self.x_pinWidth_nm / 1000.0 + self.x_pinHeight_um = self.x_pinHeight_nm / 1000.0 + self.y_pinWidth_um = self.y_pinWidth_nm / 1000.0 + self.y_pinHeight_um = self.y_pinHeight_nm / 1000.0 - # From JSON configuration file - self.tech_nm = int(json_data['tech_nm']) - self.metalPrefix = str(json_data['metalPrefix']) - self.pinWidth_nm = int(json_data['pinWidth_nm']) - self.pinPitch_nm = int(json_data['pinPitch_nm']) - self.voltage = str(json_data['voltage']) - if (self.tech_nm == 7): - # Required from JSON if tech nm is 7 - self.fin_pitch_nm = int(json_data['fin_pitch_nm']) - self.metal_track_pitch_nm = int(json_data['metal_track_pitch_nm']) - self.contacted_poly_pitch_nm = int(json_data['contacted_poly_pitch_nm']) - self.column_mux_factor = int(json_data['column_mux_factor']) - - # Optional keys - self.snapWidth_nm = int(json_data['snapWidth_nm']) if 'snapWidth_nm' in json_data else 1 - self.snapHeight_nm = int(json_data['snapHeight_nm']) if 'snapHeight_nm' in json_data else 1 - self.flipPins = str(json_data['flipPins']) if 'flipPins' in json_data else 'false' - self.pinHeight_nm = int(json_data['pinHeight_nm']) if 'pinHeight_nm' in json_data else (self.pinWidth_nm) # Default to square pins - self.vlogTimingCheckSignalExpansion = bool(json_data['vlogTimingCheckSignalExpansion']) if 'vlogTimingCheckSignalExpansion' in json_data else False + self.snapWidth_um = self.snapWidth_nm / 1000.0 + self.snapHeight_um = self.snapHeight_nm / 1000.0 - # Converted values - self.tech_um = self.tech_nm / 1000.0 - self.pinWidth_um = self.pinWidth_nm / 1000.0 - self.pinHeight_um = self.pinHeight_nm / 1000.0 - self.pinPitch_um = self.pinPitch_nm / 1000.0 + # TODO : From original bsg fakeram + # self.vlogTimingCheckSignalExpansion = bool(json_data['vlogTimingCheckSignalExpansion']) if 'vlogTimingCheckSignalExpansion' in json_data else False + # self.metal_track_pitch_um = self.metal_track_pitch_nm / 1000.0 + # if (self.pin_pitch_nm % self.metal_track_pitch_nm != 0) : + # print("Pin Pitch %d not a multiple of Metal Track Pitch %d" %(self.pin_pitch_nm,self.metal_track_pitch_nm)) + # sys.exit(1) + if (self.pinPitch_nm % self.manufacturing_grid_nm != 0) : + print("Pin Pitch %d not a multiple of Manufacturing Grid %d" %(self.pinPitch_nm, self.manufacturing_grid_nm)) + sys.exit(1) \ No newline at end of file diff --git a/scripts/utils/gen_lef/README.md b/scripts/utils/gen_lef/README.md new file mode 100644 index 0000000..e78becb --- /dev/null +++ b/scripts/utils/gen_lef/README.md @@ -0,0 +1,70 @@ +# LEF Generator for SRAM + +A Python-based Library Exchange Format (LEF) generator for creating FakeRAM macro files used in ASIC design flows. + +## Project Structure + +``` +utils/gen_lef/ +├── lef_core.py # Main entry point +├── lef_globals.py # Global utilities +├── decimal_helpers.py # Decimal arithmetic utilities +├── logic/ # Core generation logic +│ ├── gen_ports.py # Port pin placement +│ ├── gen_pinlist.py # Pin list generation +│ ├── lef_writers.py # LEF file writing +│ └── pin_wrappers.py # Pin operation wrappers +├── modules/ # Base classes +│ ├── class_lefparameters.py # LEF parameters +│ └── class_pingrid.py # Pin grid management +└── coordinates/ # Coordinate utilities + └── snap_height.py # Track alignment +``` + +## Core Modules + +### Main Entry Point +- **`lef_core.py`** - Main function that orchestrates LEF generation + +### Logic Layer +- **`gen_ports.py`** - Handles SRAM port pin placement (control, address, data) + - Manages read/write/read-write ports + - Handles pin assignment across multiple sides + - Validates pin overlap and placement + +- **`gen_pinlist.py`** - Generates equidistant pin lists + - Creates sectioned pin distributions + - Handles whole-side pin placement + - Manages pin pitch and spacing + +- **`lef_writers.py`** - Writes LEF file content + - Generates LEF header and properties + - Creates power grid straps (VDD/VSS) + - Writes pin rectangles and obstruction layers + +- **`pin_wrappers.py`** - Wrapper classes for pin operations + - `PinListWrapper`: Manages pin placement parameters + - `PinIndexWrapper`: Writes pins by index with safety checks + +### Base Classes +- **`class_lefparameters.py`** - Centralizes LEF generation parameters + - Process technology settings + - Pin dimensions and pitches + - Metal layer configurations + +- **`class_pingrid.py`** - Manages pin grid creation and validation + - Creates per-edge pin slots (top/bottom/left/right) + - Validates manufacturing grid alignment + - Handles pin pitch constraints + +### Utilities +- **`decimal_helpers.py`** - Float-safe arithmetic using Decimal + - Prevents floating-point drift + - Provides consistent rounding for layout calculations + +- **`snap_height.py`** - Track alignment utilities + - Aligns pins to routing tracks + - Snaps macro height to manufacturing grid + +- **`lef_globals.py`** - Global utility functions + - Grid snapping functions \ No newline at end of file diff --git a/scripts/utils/gen_lef/__init__.py b/scripts/utils/gen_lef/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/utils/gen_lef/coordinates/__init__.py b/scripts/utils/gen_lef/coordinates/__init__.py new file mode 100644 index 0000000..52d06cc --- /dev/null +++ b/scripts/utils/gen_lef/coordinates/__init__.py @@ -0,0 +1,2 @@ +from .snap_height import * + diff --git a/scripts/utils/gen_lef/coordinates/snap_height.py b/scripts/utils/gen_lef/coordinates/snap_height.py new file mode 100644 index 0000000..5ad8c5e --- /dev/null +++ b/scripts/utils/gen_lef/coordinates/snap_height.py @@ -0,0 +1,124 @@ + +from utils.gen_lef.lef_globals import snap_to_grid + +################################################################################ +# TRACK ALIGNMENT HELPERS +# +# Only when heightSnapPinPitch is TRUE: +# Pins and macro height are aligned to routing tracks. +# +# Functions: +# align_track_tb_pin() - For top/bottom pins, centers the pin on the nearest +# Y-track using (track_offset_y, track_pitch_y) and +# returns (y_bottom, y_top) for the pin rectangle. +# +# snap_height_to_track() - Snaps pin height to manufacturing grid, aligns the +# bottom pin center to a Y-track, computes the top pin +# center on a Y-track so all rows fit, and increases +# macro height as needed; returns the final snapped height. +################################################################################ + +def align_track_tb_pin(mem + , y_edge + , pinHeight + , track_offset_y + , track_pitch_y + , pin_name + , side ) -> tuple: + heightSnapPinPitch = mem.process.heightSnapPinPitch + widthSnapPinPitch = mem.process.widthSnapPinPitch + + if heightSnapPinPitch == True: + if side == 'top': + y_top = y_edge + y_bottom = y_top - pinHeight + aligned_y_center = y_top - (pinHeight / 2) + elif side == 'bottom': + y_bottom = y_edge + y_top = y_bottom + pinHeight + aligned_y_center = y_bottom + (pinHeight / 2) + + n_y = round((aligned_y_center - track_offset_y) / track_pitch_y) + expected_center = track_offset_y + n_y * track_pitch_y + + elif heightSnapPinPitch == False: + # TRUE FORCE OFFSET: No rounding, use exact edge-relative positioning + if side == 'top': + # For top pins, maintain exact distance from top edge + y_top = y_edge + y_bottom = y_top - pinHeight + + elif side == 'bottom': + # For bottom pins, maintain exact distance from bottom edge + y_bottom = y_edge + y_top = y_bottom + pinHeight + + return y_bottom, y_top + +def snap_height_to_track(mem + , h + , scaled_y_pitch + ) -> float: + heightSnapPinPitch = mem.process.heightSnapPinPitch + y_offset = mem.process.y_pinOffset_um + manufacturing_grid_um = mem.process.manufacturing_grid_um + pin_height = mem.process.x_pinHeight_um + + """adjust macro height to fit""" + if heightSnapPinPitch == True: + pinHeight = snap_to_grid(float(pin_height), manufacturing_grid_um) + track_pitch_y = scaled_y_pitch + track_offset_y = float(y_offset) + + required_bot_center = pinHeight / 2 + + n_bot = round((required_bot_center - track_offset_y) / track_pitch_y) + bot_center_on_track = track_offset_y + n_bot * track_pitch_y + + if bot_center_on_track - (pinHeight / 2) < 0: + shift_needed = (pinHeight / 2) - bot_center_on_track + track_offset_y += shift_needed + bot_center_on_track = pinHeight / 2 + y_offset = track_offset_y + + max_possible_tracks = int((h - pinHeight) / track_pitch_y) + 1 + + top_center_on_track = bot_center_on_track + (max_possible_tracks - 1) * track_pitch_y + required_macro_height = top_center_on_track + (pinHeight / 2) + + if required_macro_height < h: + top_center_on_track += track_pitch_y + required_macro_height = top_center_on_track + (pinHeight / 2) + + final_height = snap_to_grid(required_macro_height, manufacturing_grid_um) + + elif heightSnapPinPitch == False: + # TRUE FORCE OFFSET: Keep original height, extend if needed + pinHeight = snap_to_grid(float(pin_height), manufacturing_grid_um) + track_offset_y = float(y_offset) + + # Calculate potential pin positions with exact offset + bottom_pin_center = pinHeight / 2 # First pin center + top_pin_center = bottom_pin_center + ((h - pinHeight) // scaled_y_pitch) * scaled_y_pitch + + # Check if pins would extend beyond macro bounds + bottom_edge = bottom_pin_center - (pinHeight / 2) + top_edge = top_pin_center + (pinHeight / 2) + + # Extend macro if needed + if bottom_edge < 0: + extension_needed = abs(bottom_edge) + final_height = h + extension_needed + mem._bottom_edge_offset = bottom_edge + elif top_edge > h: + extension_needed = top_edge - h + final_height = h + extension_needed + mem._bottom_edge_offset = 0 + else: + final_height = h + mem._bottom_edge_offset = 0 + + final_height = snap_to_grid(final_height, manufacturing_grid_um) + + + return final_height \ No newline at end of file diff --git a/scripts/utils/gen_lef/decimal_helpers.py b/scripts/utils/gen_lef/decimal_helpers.py new file mode 100644 index 0000000..6b3ec3c --- /dev/null +++ b/scripts/utils/gen_lef/decimal_helpers.py @@ -0,0 +1,180 @@ +from decimal import Decimal, ROUND_HALF_DOWN + +################################################################################ +# DECIMAL HELPERS +# +# Purpose: +# Float-safe, deterministic arithmetic for layout math (e.g., microns/tracks). +# Uses Decimal with ROUND_HALF_DOWN to avoid FP drift and produce stable +# grid/pitch snapping. +# +# Defaults: +# - Results rounded to 3 decimal places unless noted. +# - *_round_first_fpoint -> rounds to 0.1 +# - *_round_second_fpoint -> rounds to 0.01 +# +# Functions: +# Multiply: d_get_multiple_val() # round down to nearest multiple +# d_get_multiply() +# +# Divide: d_get_divide() +# Add: d_get_add(), d_get_add_round_first_fpoint(), d_get_add_round_second_fpoint() +# +# Subtract: d_get_subtract(), d_get_subtract_round_first_fpoint(), d_get_subtract_round_second_fpoint() +# +# Other: d_get_round_to_int(), d_get_abs(), d_get_min(), d_get_max(), +# d_get_floor(), d_get_ceil(), d_get_modulo() +################################################################################ + +### Multiply Functions +def d_get_multiply(val1 : float, val2 : float) -> float: + """Multiply val1 with val2 + + :quantize: '0.001' + :rounds: ROUND_HALF_DOWN + """ + d_mult_val = Decimal(str(val1)) * Decimal(str(val2)) + return float(d_mult_val.quantize(Decimal('0.001'), rounding=ROUND_HALF_DOWN)) + +### Divide Functions +def d_get_divide(val1 : float, val2 : float) -> float: + """Divide val1 with val2 + + :quantize: '0.001' + :rounds: ROUND_HALF_DOWN + """ + d_div_val = Decimal(str(val1)) / Decimal(str(val2)) + return float(d_div_val.quantize(Decimal('0.001'), rounding=ROUND_HALF_DOWN)) + +### Add Functions +def d_get_add(val1 : float, val2 : float) -> float: + """Add val1 with val2 + + :quantize: '0.001' + :rounds: ROUND_HALF_DOWN + """ + d_add_val = Decimal(str(val1)) + Decimal(str(val2)) + return float(d_add_val.quantize(Decimal('0.001'), rounding=ROUND_HALF_DOWN)) + +def d_get_add_round_second_fpoint(val1 : float, val2 : float) -> float: + """Add val1 with val2 + + :quantize: '0.01' + :rounds: ROUND_HALF_DOWN + """ + d_add_val = Decimal(str(val1)) + Decimal(str(val2)) + return float(d_add_val.quantize(Decimal('0.01'), rounding=ROUND_HALF_DOWN)) + +def d_get_add_round_first_fpoint(val1 : float, val2 : float) -> float: + """Add val1 with val2 + + :quantize: '0.1' + :rounds: ROUND_HALF_DOWN + """ + d_add_val = Decimal(str(val1)) + Decimal(str(val2)) + return float(d_add_val.quantize(Decimal('0.1'), rounding=ROUND_HALF_DOWN)) + + +### Subtract Functions +def d_get_subtract(val1 : float, val2 : float) -> float: + """Subtract val1 with val2 + + :quantize: '0.001' + :rounds: ROUND_HALF_DOWN + """ + d_sub_val = Decimal(str(val1)) - Decimal(str(val2)) + return float(d_sub_val.quantize(Decimal('0.001'), rounding=ROUND_HALF_DOWN)) + +def d_get_subtract_round_second_fpoint(val1 : float, val2 : float) -> float: + """Subtract val1 with val2 + + :quantize: '0.01' + :rounds: ROUND_HALF_DOWN + """ + d_add_val = Decimal(str(val1)) - Decimal(str(val2)) + return float(d_add_val.quantize(Decimal('0.01'), rounding=ROUND_HALF_DOWN)) + +def d_get_subtract_round_first_fpoint(val1 : float, val2 : float) -> float: + """Subtract val1 with val2 + + :quantize: '0.1' + :rounds: ROUND_HALF_DOWN + """ + d_add_val = Decimal(str(val1)) - Decimal(str(val2)) + return float(d_add_val.quantize(Decimal('0.1'), rounding=ROUND_HALF_DOWN)) + + +### Other Functions +def d_get_multiple_val(number : float, multiple : float) -> float: + """ Round number down to nearest multiple + + :quantize: '0.001' + :rounds: ROUND_HALF_DOWN + :number: + :multiple: + + :description: + return (number // multiple) * multiple + """ + d_number = Decimal(str(number)) + d_multiple = Decimal(str(multiple)) + if d_multiple == 0: + return float(d_number) + result = (d_number // d_multiple) * d_multiple + return float(result.quantize(Decimal('0.001'), rounding=ROUND_HALF_DOWN)) + +def d_get_round_to_int(val : float) -> float: + d_val = Decimal(str(val)) + return int(d_val.quantize(Decimal('1'), rounding=ROUND_HALF_DOWN)) + +def d_get_abs(val : float) -> float: + """Get absolute value using decimal + + :quantize: '0.001' + :rounds: ROUND_HALF_DOWN""" + d_val = Decimal(str(val)) + return float(d_val.__abs__().quantize(Decimal('0.001'), rounding=ROUND_HALF_DOWN)) + +def d_get_min(val1 : float, val2 : float) -> float: + """Get minimum of two values using decimal + + :quantize: '0.001' + :rounds: ROUND_HALF_DOWN""" + d_val1 = Decimal(str(val1)) + d_val2 = Decimal(str(val2)) + return float(min(d_val1, d_val2).quantize(Decimal('0.001'), rounding=ROUND_HALF_DOWN)) + +def d_get_max(val1 : float, val2 : float) -> float: + """Get maximum of two values using decimal + + :quantize: '0.001' + :rounds: ROUND_HALF_DOWN""" + d_val1 = Decimal(str(val1)) + d_val2 = Decimal(str(val2)) + return float(max(d_val1, d_val2).quantize(Decimal('0.001'), rounding=ROUND_HALF_DOWN)) + +def d_get_floor(val : float) -> float: + """Get floor value using decimal + + :quantize: '0.001' + :rounds: ROUND_HALF_DOWN""" + d_val = Decimal(str(val)) + return float(d_val.quantize(Decimal('1'), rounding=ROUND_HALF_DOWN)) + +def d_get_ceil(val : float) -> float: + """Get ceiling value using decimal + + :quantize: '1' + :rounds: ROUND_HALF_DOWN""" + d_val = Decimal(str(val)) + return float((d_val + Decimal('0.999')).quantize(Decimal('1'), rounding=ROUND_HALF_DOWN)) + +def d_get_modulo(val1 : float, val2 : float) -> float: + """Get modulo using decimal + + :quantize: '0.001' + :rounds: ROUND_HALF_DOWN""" + d_val1 = Decimal(str(val1)) + d_val2 = Decimal(str(val2)) + result = d_val1 % d_val2 + return float(result.quantize(Decimal('0.001'), rounding=ROUND_HALF_DOWN)) \ No newline at end of file diff --git a/scripts/utils/gen_lef/lef_core.py b/scripts/utils/gen_lef/lef_core.py new file mode 100644 index 0000000..2b3c275 --- /dev/null +++ b/scripts/utils/gen_lef/lef_core.py @@ -0,0 +1,22 @@ + +from utils.gen_lef.modules import * +from utils.gen_lef.logic import * + +################################################################################ +# CREATE LEF view for the given SRAM +################################################################################ + +def generate_lef(mem): + LEF_Parameters(mem) + + lef_writer = LEF_WriteFunctions(mem) + + port_generator = GeneratePorts(mem) + + lef_writer.gen_header() + + port_generator.generate_ports() + + lef_writer.gen_straps() + + lef_writer.gen_obs() diff --git a/scripts/utils/gen_lef/lef_globals.py b/scripts/utils/gen_lef/lef_globals.py new file mode 100644 index 0000000..70de1a9 --- /dev/null +++ b/scripts/utils/gen_lef/lef_globals.py @@ -0,0 +1,9 @@ +from decimal import Decimal, ROUND_HALF_UP + +def snap_to_grid(value, grid): + """ snap to grid using decimal """ + d_value = Decimal(str(value)) + d_grid = Decimal(str(grid)) + grids = d_value / d_grid + rounded_grids = grids.quantize(Decimal('1'), rounding=ROUND_HALF_UP) + return float(rounded_grids * d_grid) diff --git a/scripts/utils/gen_lef/logic/__init__.py b/scripts/utils/gen_lef/logic/__init__.py new file mode 100644 index 0000000..4f7892e --- /dev/null +++ b/scripts/utils/gen_lef/logic/__init__.py @@ -0,0 +1,2 @@ +from utils.gen_lef.logic.lef_writers import * +from utils.gen_lef.logic.gen_ports import * \ No newline at end of file diff --git a/scripts/utils/gen_lef/logic/gen_pinlist.py b/scripts/utils/gen_lef/logic/gen_pinlist.py new file mode 100644 index 0000000..008fcf0 --- /dev/null +++ b/scripts/utils/gen_lef/logic/gen_pinlist.py @@ -0,0 +1,280 @@ + +import traceback + +from utils.gen_lef.decimal_helpers import * +from utils.gen_lef.modules.class_pingrid import PinGrid, PinSlot + +################################################################################ +# GENERATE PIN LIST HELPERS +# +# Utilizes PinGrid Class and provides helpers to select equidistant pins within +# a section or across a whole side. +# +# Functions: +# generate_equidistant_sectioned_pins() - choose pins within [start,end] section +# get_equidistant_whole_side_pins() - choose pins across whole side w/ margin +# _get_side_parameters() - return slots, dimension, and pin pitch +################################################################################ + +class GeneratePinList(PinGrid): + def __init__(self, mem): + super().__init__(mem) + + # DEBUG PARAMS + self.gen_whole_side_list_debug : bool = False + self.gen_section_list_debug : bool = False + +#### Private Functions +#--------------------- + def _get_side_parameters(self + , side : str) -> tuple[list, float, float]: + """Takes in parameter "side" if valid + + Returns + available_slots - side's all available slots + + dimension - side's dimension (height/width) + + pin_pitch - side's x or y pitch + """ + if side == 'top': + available_slots = self.list_top_pins + dimension = self.w + pin_pitch = self.x_pin_pitch + elif side == 'bottom': + available_slots = self.list_bot_pins + dimension = self.w + pin_pitch = self.x_pin_pitch + elif side == 'left': + available_slots = self.list_left_pins + dimension = self.h + pin_pitch = self.y_pin_pitch + elif side == 'right': + available_slots = self.list_right_pins + dimension = self.h + pin_pitch = self.y_pin_pitch + else: + raise ValueError(f"Invalid side: {side}. Must be 'top', 'bottom', 'left', or 'right'") + return available_slots, dimension, pin_pitch + +#### Public Functions +#-------------------- + def generate_equidistant_sectioned_pins(self + , side : str + , start : float + , end : float + , metLayer : int + , num_pins : float = None + , new_section : bool = None + , padding : float = None) -> list[object]: + """ + Filter available slots in section, then distribute num_pins evenly across those indices. + Validates against available slots created by the class. + """ + if self.gen_section_list_debug: + print("="*60) + print(f"\nStarting Equidistant Section Debug CALLED: {self.num_gen_section_calls}\n") + print(f"side: {side}") + print(f"start: {start}") + print(f"end: {end}") + print(f"metLayer: {metLayer}") + print(f"num_pins: {num_pins}") + print(f"new_section: {new_section}") + print(f"padding: {padding}\n") + print("="*60) + self.num_gen_section_calls += 1 + + if new_section is None: + new_section = False + + if padding is None: + padding = 0.001 + + pin_list = [] + available_slots, dimension, pin_pitch = self._get_side_parameters(side) + pin_width = self.y_pin_width if (side == 'left' or side == 'right') else self.x_pin_width + + if new_section == True: + actual_start = d_get_multiply(dimension, d_get_add(start, padding)) + actual_end = d_get_multiply(dimension, d_get_add(end, padding)) + else: + actual_start = d_get_multiply(dimension, start) + actual_end = d_get_multiply(dimension, end) + section_length = d_get_subtract(actual_end, actual_start) + + # Filter valid slots + valid_slots = [] + for slot in available_slots: + + # Valid if between starting and end points of SRAM, + # if unused and not on same metal layer + if (actual_start <= slot.slot <= actual_end and + slot.used == False and + slot.metLayer == metLayer): + valid_slots.append(slot) + + if not valid_slots: + traceback.print_stack() + raise SystemExit( + f"ERROR: Pins at {side} Calling num_pins: {num_pins}\n" + f"ERROR: No valid slots in the specified section!\n" + ) + + if self.gen_section_list_debug: + unique_coords = set(slot.slot for slot in valid_slots) + print(f"DEBUG: Unique coordinates in valid_slots: {len(unique_coords)}") + print(f"DEBUG: Total valid_slots: {len(valid_slots)}") + if len(unique_coords) != len(valid_slots): + print("WARNING: Duplicate coordinates found in valid_slots!") + + if num_pins is None: + num_pins = int(d_get_divide(section_length, pin_pitch)) + if num_pins < 1: + num_pins = 1 + num_pins = min(num_pins, len(valid_slots)) + else: + num_pins = min(num_pins, len(valid_slots)) + + if num_pins <= 0: + return pin_list + + # Divide evenly across available slot indices + if num_pins >= len(valid_slots): + selected_indices = list(range(len(valid_slots))) + elif num_pins == 1: + selected_indices = [len(valid_slots) // 2] + else: + selected_indices = [] + total_slots = len(valid_slots) + + if (total_slots - 1) % (num_pins - 1) == 0: + step = (total_slots - 1) // (num_pins - 1) + for i in range(num_pins): + if i == num_pins - 1: + idx = total_slots - 1 + else: + idx = i * step + selected_indices.append(idx) + else: + step_size = d_get_divide(d_get_subtract(total_slots, 1), d_get_subtract(num_pins, 1)) + for i in range(num_pins): + if i == 0: + idx = 0 + elif i == num_pins - 1: + idx = total_slots - 1 + else: + idx = d_get_round_to_int(d_get_multiply(i, step_size)) + selected_indices.append(idx) + + selected_slots = [valid_slots[i] for i in selected_indices] + + # Debug output + if self.gen_section_list_debug: + print(f"DEBUG: Section {start:.3f}-{end:.3f} = {actual_start:.3f}-{actual_end:.3f}, length={section_length:.3f}") + print(f"DEBUG: Found {len(valid_slots)} valid slots in section") + print(f"DEBUG: Requesting {num_pins} pins from {len(valid_slots)} available slots") + print(f"DEBUG: Even distribution indices: {selected_indices}") + print(f"DEBUG: Final selected indices: {selected_indices}") + selected_positions = [d_get_add(s.slot, d_get_divide(pin_width, 2)) for s in selected_slots] + print(f"DEBUG: Selected {len(selected_slots)} slot positions: {[f'{p:.3f}' for p in selected_positions]}") + if len(selected_positions) > 1: + spacings = [d_get_subtract(selected_positions[i+1], selected_positions[i]) for i in range(len(selected_positions)-1)] + print(f"DEBUG: Spacings between pins: {[f'{s:.3f}' for s in spacings]}") + print(f"DEBUG: Min spacing: {min(spacings):.3f}, Max spacing: {max(spacings):.3f}") + spacing_range = d_get_subtract(max(spacings), min(spacings)) + print(f"DEBUG: Spacing uniformity (max-min): {spacing_range:.3f}") + + # Create slot object in selected slots + for slot in selected_slots: + new_slot = PinSlot( + slot=slot.slot, + used=True, + side=side, + metLayer=metLayer + ) + pin_list.append(new_slot) + slot.used = True + return pin_list + + def get_equidistant_whole_side_pins(self + , side : str + , metLayer : int + , margin : float = None + , num_pins : int = None + , pin_list : list = None + , min_distance : int = None) -> list[object]: + """ + Generate equidistant pins across whole side with margin. + """ + available_slots, dimension, pin_pitch = self._get_side_parameters(side) + + if pin_list is None: + pin_list = [] + if margin is None: + margin = 0.1 + if min_distance is None: + min_distance = 1 + + section_start = margin + section_end = d_get_subtract(1, margin) + actual_start = d_get_multiply(dimension, section_start) + actual_end = d_get_multiply(dimension, section_end) + section_length = d_get_subtract(actual_end, actual_start) + + # Filter valid slots + valid_slots = [] + for slot in available_slots: + + # Valid if between starting and end points of SRAM, + # if unused and not on same metal layer + if (actual_start <= slot.slot <= actual_end and + slot.used == False and + slot.metLayer == metLayer): + valid_slots.append(slot) + + if not valid_slots: + traceback.print_stack() + raise SystemExit("ERROR: No valid slots in the specified section!") + + if num_pins is None: + num_pins = int(d_get_divide(section_length, pin_pitch)) + if num_pins < 1: + num_pins = 1 + num_pins = min(num_pins, len(valid_slots)) + else: + num_pins = min(num_pins, len(valid_slots)) + + if num_pins <= 0: + return pin_list + + selected_slots = [] + + # Equidistant placement + if num_pins == 1: + middle_idx = len(valid_slots) // 2 + selected_slots.append(valid_slots[middle_idx]) + else: + step = (len(valid_slots) - 1) / (num_pins - 1) + for i in range(num_pins): + idx = int(round(i * step)) + selected_slots.append(valid_slots[idx]) + + for slot in selected_slots: + new_slot = PinSlot( + slot=slot.slot, + used=True, + side=side, + metLayer=metLayer + ) + pin_list.append(new_slot) + for orig_slot in available_slots: + if abs(orig_slot.slot - slot.slot) < 0.001: + orig_slot.used = True + break + + if self.gen_whole_side_list_debug: + print(f"DEBUG: Selected {len(selected_slots)} equidistant pins for side {side}") + positions = [slot.slot for slot in selected_slots] + print(f"DEBUG: Pin positions: {[f'{p:.3f}' for p in positions]}") + + return pin_list diff --git a/scripts/utils/gen_lef/logic/gen_ports.py b/scripts/utils/gen_lef/logic/gen_ports.py new file mode 100644 index 0000000..724aba6 --- /dev/null +++ b/scripts/utils/gen_lef/logic/gen_ports.py @@ -0,0 +1,573 @@ + +import traceback + +from collections import deque +from utils.gen_lef.decimal_helpers import * +from utils.gen_lef.modules.class_pingrid import PinGrid +from utils.gen_lef.logic.gen_pinlist import GeneratePinList +from utils.gen_lef.logic.pin_wrappers import PinIndexWrapper, PinListWrapper + +################################################################################ +# GENERATE PORT CLASS +# +# This class generates SRAM port pin placement (control / address / data), +# using class PinListWrapper to build prevalidated slot lists and +# class PinIndexWrapper to write LEF pins by index; tech params via +# class LEF_PinGrid. +# +# Functions: +# write_rport_control_pin() - Writes pin rport control pin +# write_rport_addr_in_pin() - Writes pin rport addr in pin +# write_rport_rd_out_pin() - Writes pin rport rd out pin +# write_wport_control_pin() - Writes pin wport control pin +# write_wport_addr_in_pin() - Writes pin wport addr in pin +# write_wport_wd_in_pin() - Writes pin wport wd in pin +# write_wmask_in_pin() - Writes pin wmask in pin +# _is_overlapped(id, pin_list, i) - Validates pin if overlapped +# generate_ports() - Order of write port functions +# +# Notes: +# - Threshold fields are defined but currently unused (reserved for tuning). +# - _is_overlapped() aborts on same-slot/same-layer collisions. +################################################################################ + +class GeneratePorts(GeneratePinList + , PinIndexWrapper + , PinListWrapper): + def __init__(self, mem): + super().__init__(mem) + + self.r_ports_dict = {"rw": self.num_rwports, "r": self.num_rports} + self.w_ports_dict = {"rw": self.num_rwports, "w": self.num_wports} + self.r_control_pins = ["clk", "ce_in"] + self.w_control_pins = ["clk", "ce_in", "we_in"] + self.is_rw_port_addr_in_written = False + self.w_num_control_pins = len(self.w_control_pins) + self.r_num_control_pins = len(self.r_control_pins) + self.w_control_threshold_left = 3 # Unused + self.r_control_threshold_left = 3 # Unused + self.r_addr_in_threshold_left = 6 # Unused + self.w_addr_in_threshold_left = 6 # Unused + self.is_rd_out_threshold = False # Unused + self.w_is_addr_threshold = False # Unused + self.r_is_addr_threshold = False # Unused + self.r_is_control_threshold = False # Unused + self.w_is_control_threshold = False # Unused + + self.sides = ['left' + , 'top' + , 'right' + , 'bottom' + ] + + self.ports_written = self._init_ports_written() + + self.is_rw_port_control_written = False + + self._update_section_placement() + +#### Private Functions +#--------------------- + def _init_ports_written(self + , ports_written: dict = None + , all_available_pins: list = ['clk', 'ce_in', 'addr_in', 'rd_out', 'we_in', 'wd_in']) -> dict[str, bool]: + """ + Initialize port dictionary to track if ports are written. + Returns dictionary of all port pins with boolean values set to False. + """ + if ports_written is None: + ports_written = {} + + # Define which pins are excluded for each port type + pin_exclusions = { + 'r': {'we_in', 'wd_in'}, + 'w': {'rd_out'}, + 'rw': set() # No exclusions for read-write ports + } + + # Define which pins need bit indexing + bit_indexed_pins = { + 'addr_in': self.addr_width, + 'rd_out': self.bits, + 'wd_in': self.bits + } + + for port_type, num_ports in self.mem.sram_data['ports'].items(): + excluded_pins = pin_exclusions.get(port_type, set()) + for port_num in range(num_ports): + for pin in all_available_pins: + if pin in excluded_pins: + continue + base_name = f"{port_type}{port_num}_{pin}" + if pin in bit_indexed_pins: + bit_count = bit_indexed_pins[pin] + for bit in range(bit_count): + ports_written[f"{base_name}[{bit}]"] = False + else: + ports_written[base_name] = False + return ports_written + + def _is_port_written(self + , port_name : str + , port_type_index : int + , pin : str + , pin_bit_index : int = None) -> bool: + """ returns true if given port value is true """ + if pin_bit_index != None: + return self.ports_written[f"{port_name}{port_type_index}_addr_in[{pin_bit_index}]"] == True + else: + return self.ports_written[f"{port_name}{port_type_index}_{pin}"] == True + + def _update_section_placement(self): + """ Update section placement based on port configuration """ + total_w_ports = sum(self.w_ports_dict.values()) + + w_addr_total_padding = 0 + if total_w_ports > 1: + w_addr_total_padding = (total_w_ports - 1) * self.right_group_padding + + w_addr_effective_end = self.w_addr_in_section_end + w_addr_total_padding + + if self.is_r_port_and_w_port: + print("INFO: Detected both R and RW ports - adjusting R address section") + r_addr_section_height = d_get_subtract_round_second_fpoint( + self.r_addr_in_section_end, self.r_addr_in_section_start + ) + self.padding = 0.1 + + self.r_addr_in_section_start = w_addr_effective_end + self.padding + self.r_addr_in_section_end = self.r_addr_in_section_start + r_addr_section_height + + pure_r_ports = self.r_ports_dict.get("r", 0) + if pure_r_ports > 1: + r_addr_total_padding = (pure_r_ports - 1) * self.left_group_padding + self.r_addr_in_section_end += r_addr_total_padding + + print(f"INFO: Adjusted R addr section to: {self.r_addr_in_section_start:.3f} - {self.r_addr_in_section_end:.3f}") + print(f"INFO: W addr section (with RW): {self.w_addr_in_section_start:.3f} - {w_addr_effective_end:.3f}") + + # Handle case where only write ports exist + if not self.is_r_port_or_rw_port: + self.w_control_side = 'left' + self.w_control_section_start = self.r_control_section_start + self.w_control_section_end = self.r_control_section_end + + self.w_addr_in_side = 'left' + self.w_addr_in_section_start = self.r_addr_in_section_start + self.w_addr_in_section_end = self.r_addr_in_section_end + print('INFO: Left side available for write address and control pins') + + self.wd_in_side = "top" + print('INFO: Top side available for write data pins') + + def _strip_port_index(self + , key : str) -> str: + """ Remove trailing digits from the port base (before the first underscore) """ + if "_" not in key: + return key.rstrip("0123456789") + + prefix = key.split("_", 1)[0] + base = prefix.rstrip("0123456789") + rest = key.split("_", 1)[1] + return f"{base}_{rest}" + + def _is_overlapped(self + , id : str + , pin_list : list[object] + , i : int ) -> None : + """ checks if pin is overlapped with any previous/current pins""" + current_pin = pin_list[i] + for j in range(i): + other_pin = pin_list[j] + if (abs(current_pin.slot - other_pin.slot) < 0.001 and + current_pin.metLayer == other_pin.metLayer): + traceback.print_stack() + print(f"ERROR: {id} overlaps with {pin_list[j]} at slot {current_pin.slot}, layer {current_pin.metLayer}") + raise SystemExit('Exiting with error.') + +#### Public Functions +#-------------------- + def write_rport_control_pin(self): + """ + Write read and read-write port control pins based on threshold, banks, and group spacing. + Handles special case if there are any read-write ports of the given sram will delete from + local dictionary, then marking `is_rw_port_control_written` to False, leaving + `write_wport_control_pin` function to write rw port to the right side. + """ + total_r_port_control_pins = 0 + r_ports_dict = self.r_ports_dict.copy() + + if self.is_r_port_and_rw_port: + del r_ports_dict["rw"] + self.is_rw_port_control_written = False + + total_r_ports = sum(r_ports_dict.values()) + for curr_port_num in range(0, total_r_ports): + pin_list = self.get_list_sectioned_r_control_pins_wrapper( + curr_port_num + , self.r_num_control_pins + ) + port_name = None + port_type_index = None + current_port_index = 0 + for key, value in r_ports_dict.items(): + if curr_port_num < current_port_index + value: + port_name = key + port_type_index = curr_port_num - current_port_index + break + current_port_index += value + + if port_name is None: + traceback.print_stack() + print(f"ERROR: Could not determine port name for port index {curr_port_num}") + raise SystemExit("Exiting with error") + + for i, pin in enumerate(self.r_control_pins): + if self._is_port_written(port_name, port_type_index, pin): + continue + self._is_overlapped(f"{port_name}{port_type_index}_{pin}", pin_list, i) + + pin_used = self.get_index_write_vertical_input_wrapper( + pin_id = f"{port_name}{port_type_index}_{pin}", + side = self.r_control_side, + index = i, + pin_list = pin_list + ) + self.ports_written[f"{port_name}{port_type_index}_{pin}"] = True # mark here as used + total_r_port_control_pins += pin_used + + if total_r_port_control_pins > self.r_control_threshold_left: + pass + if self.banks > 1: + pass + print(f"Total read control pins placed: {total_r_port_control_pins}") + return + + def write_rport_addr_in_pin(self): + """ + Write read and read-write port address input pins based on threshold, banks, and group spacing. + Handles special case if there are any read-write ports of the given sram will delete from + local dictionary, then marking `is_rw_port_addr_written` to False, leaving + `write_wport_addr_in_pin` function to write rw port to the right side. + """ + total_r_addr_in_pins = 0 + + # Delete from local r port dictionary + # Let w_addr_in generate rw's w_addr_in + # Assuming rports has priority in generation + r_ports_dict = self.r_ports_dict.copy() + + if self.is_r_port_and_rw_port: + del r_ports_dict["rw"] + self.is_rw_port_addr_in_written = False + + total_r_ports = sum(r_ports_dict.values()) + + for curr_port_num in range(0, total_r_ports): + pin_list = self.get_list_sectioned_r_addr_pins_wrapper( + curr_port_num, + ) + port_name = None + port_type_index = None + current_port_index = 0 + for key, value in r_ports_dict.items(): + if curr_port_num < current_port_index + value: + port_name = key + port_type_index = curr_port_num - current_port_index # Offset within this port type + break + current_port_index += value + + if port_name is None: + print(f"ERROR: Could not determine port name for port index {curr_port_num}") + traceback.print_stack() + raise SystemExit("Exiting with error") + + for pin_bit_index in range(0, self.addr_width): + if self._is_port_written(port_name + , port_type_index + , "addr_in" + , pin_bit_index=pin_bit_index): + continue + self._is_overlapped(f"{port_name}{port_type_index}_addr_in[{pin_bit_index}]", pin_list, pin_bit_index) + + pin_used = self.get_index_write_vertical_input_wrapper( + pin_id = f"{port_name}{port_type_index}_addr_in[{pin_bit_index}]", + side = self.r_addr_in_side, + index = pin_bit_index, + pin_list = pin_list + ) + self.ports_written[f"{port_name}{port_type_index}_addr_in[{pin_bit_index}]"] = True # mark here as used + total_r_addr_in_pins += pin_used + + if total_r_addr_in_pins > self.r_addr_in_threshold_left: + """ + break when pins exceeded area of control logic + section area. + """ + # self.r_control_side = 'right' + # section_start, section_end = 0.8 , 0.85 + # self.r_is_control_threshold = True + pass + + if self.banks == 2 or self.banks == 4: + """ + TODO: Bank logic + """ + # self.r_control_side = 'left' + # section_start += 0.4 + # section_end += 0.4 + pass + + print(f"Total address input pins placed: {total_r_addr_in_pins}") + return + + def write_rport_rd_out_pin(self): + """ + Write read and read-write port data output pins based on threshold, banks, and group spacing. + Handles pin assignment across multiple ports by traversing each bit of the ports data width. + Ordering goes: rw ports before r ports, and avoids overlap by validating each + generated pin location before insertion. + """ + used_r_rd_out_pins = 0 + total_rd_out_port_bits = sum(self.r_ports_dict.values()) * self.bits + + pin_list = self.get_equidistant_whole_side_rd_pins_wrapper( + self.rd_out_side, self.rd_out_metal_layer, total_rd_out_port_bits + ) + + # highest port index first (rw1, rw0, then r0, etc.) + instances = [] + for key, nports in self.r_ports_dict.items(): + for port_idx in range(nports - 1, -1, -1): # reversed so rw1 comes before rw0 + instances.append([key, port_idx, 0]) # [key, port_idx, next_bit] + + q = deque(instances) + pin_start_index = 0 + + while q: + key, port_idx, next_bit = q.popleft() + self._is_overlapped(f"{key}{port_idx}_rd_out[{next_bit}]", pin_list, pin_start_index) + pin_used = self.get_index_write_horizontal_output_wrapper( + pin_id = f"{key}{port_idx}_rd_out[{next_bit}]", + side = self.rd_out_side, + index = pin_start_index, + pin_list = pin_list, + ) + used_r_rd_out_pins += pin_used + pin_start_index += 1 + next_bit += 1 + if next_bit < self.bits: + q.append([key, port_idx, next_bit]) + +### Generate Write + def write_wport_control_pin(self): + """ + Write write and read-write port control pins based on threshold, banks, and group spacing. + Handles special case when read-write ports of the given sram if `is_rw_port_control_written` + is set to True, this function will not write read-write control pin to right side. + Additionally avoids overlap by validating each generated pin location before insertion. + """ + total_w_port_control_pins = 0 + w_port_control_padding = self.left_group_padding + w_ports_dict = self.w_ports_dict.copy() + + if self.is_rw_port_control_written: + del w_ports_dict['rw'] + + total_w_ports = sum(w_ports_dict.values()) + for curr_port_num in range(0, total_w_ports): + + pin_list = self.get_list_sectioned_w_control_pins_wrapper(curr_port_num + , self.w_num_control_pins + ) + w_port_control_padding += self.left_group_padding + + port_name = None + port_type_index = None + current_port_index = 0 + + for key, value in w_ports_dict.items(): + if curr_port_num < current_port_index + value: + port_name = key + port_type_index = curr_port_num - current_port_index + break + current_port_index += value + + if port_name is None: + print(f"ERROR: Could not determine port name for port index {curr_port_num}") + traceback.print_stack() + raise SystemExit("Exiting with error") + + for i, pin in enumerate(self.w_control_pins): + if self._is_port_written(port_name, port_type_index, pin): + continue + self._is_overlapped(f"{port_name}{port_type_index}_{pin}", pin_list, i) + + pin_used = self.get_index_write_vertical_input_wrapper( + pin_id = f"{port_name}{port_type_index}_{pin}", + side = self.w_control_side, + index = i, + pin_list = pin_list + ) + self.ports_written[f"{port_name}{port_type_index}_{pin}"] = True # mark here as used + total_w_port_control_pins += pin_used + + if total_w_port_control_pins > self.w_control_threshold_left: + pass + if self.banks > 1: + pass + print(f"Total write control pins placed: {total_w_port_control_pins}") + return + + def write_wport_addr_in_pin(self): + """ + Write write and read-write port address in pins based on threshold, banks, and group spacing. + Handles special case if there are any read-write ports of the given sram will delete from + local dictionary, then marking `is_rwport_addr_in_written` to False, leaving + `write_wport_addr_in_pin` function to write rw port to the right side. + Additionally avoids overlap by validating each generated pin location before insertion. + """ + if self.is_rw_port_addr_in_written == True: + print("INFO: rwport write attempt") + return + + total_w_addr_in_pins = 0 + total_w_ports = sum(self.w_ports_dict.values()) + for curr_port_num in range(0, total_w_ports): + pin_list = self.get_list_sectioned_w_addr_pins_wrapper( + curr_port_num, + ) + port_name = None + port_type_index = None + current_port_index = 0 + for key, value in self.w_ports_dict.items(): + if curr_port_num < current_port_index + value: + port_name = key + port_type_index = curr_port_num - current_port_index # Offset within this port type + break + current_port_index += value + + if port_name is None: + print(f"ERROR: Could not determine port name for port index {curr_port_num}") + traceback.print_stack() + raise SystemExit("Exiting with error") + + for pin_bit_index in range(0, self.addr_width): + if self._is_port_written(port_name + , port_type_index + , "addr_in" + , pin_bit_index=pin_bit_index): + continue + self._is_overlapped(f"{port_name}{port_type_index}_addr_in[{pin_bit_index}]", pin_list, pin_bit_index) + + pin_used = self.get_index_write_vertical_input_wrapper( + pin_id = f"{port_name}{port_type_index}_addr_in[{pin_bit_index}]", + side = self.w_addr_in_side, + index = pin_bit_index, + pin_list = pin_list + ) + self.ports_written[f"{port_name}{port_type_index}_addr_in[{pin_bit_index}]"] = True # mark here as used + total_w_addr_in_pins += pin_used + + if total_w_addr_in_pins > self.w_addr_in_threshold_left: + pass + + if self.banks == 2 or self.banks == 4: + pass + + print(f"Total address input pins placed: {total_w_addr_in_pins}") + return + + def write_wport_wd_in_pin(self): + """ + Write write and read-write port data output pins based on threshold, banks, and group spacing. + Handles pin assignment across multiple ports by traversing each bit of the ports data width. + Ordering goes: rw ports before w ports, and avoids overlap by validating each + generated pin location before insertion. + """ + used_w_wd_in_pins = 0 + total_wd_in_port_bits = sum(self.w_ports_dict.values()) * self.bits + + # top is free for wd in + # if self.is_r_port_or_rw_port == False: + # self.wd_in_side = "top" + + pin_list = self.get_equidistant_whole_side_wd_pins_wrapper( + self.wd_in_side, self.wd_in_metal_layer, total_wd_in_port_bits + ) + + # highest port index first (rw1, rw0, then w0, etc) + instances = [] + for key, nports in self.w_ports_dict.items(): + for port_idx in range(nports - 1, -1, -1): # reversed so rw1 comes before rw0 + instances.append([key, port_idx, 0]) + + q = deque(instances) + pin_start_index = 0 + + while q: + key, port_idx, next_bit = q.popleft() + self._is_overlapped(f"{key}{port_idx}_wd_in[{next_bit}]", pin_list, pin_start_index) + pin_used = self.get_index_write_horizontal_input_wrapper( + pin_id = f"{key}{port_idx}_wd_in[{next_bit}]", + side = self.wd_in_side, + index = pin_start_index, + pin_list = pin_list, + ) + used_w_wd_in_pins += pin_used + pin_start_index += 1 + + next_bit += 1 + if next_bit < self.bits: + q.append([key, port_idx, next_bit]) + return + + def write_wmask_in_pin(self): + if not self.has_wmask: + return + + total_wmask_in_bits = sum(self.w_ports_dict.values()) * self.num_wmasks + used_wmask_pins = 0 + pin_bit_index = 0 + + for key, curr_port_num in self.w_ports_dict.items(): + pin_list = self.get_list_sectioned_wmask_pins_wrapper(curr_port_num + , total_wmask_in_bits + ) + + for port_num in range(curr_port_num): + for bit in range(self.num_wmasks): + self._is_overlapped(f"{key}{port_num}_wmask_in[{bit}]", pin_list, bit) + pin_used = self.get_index_write_vertical_input_wrapper( + pin_id = f"{key}{port_num}_wmask_in[{bit}]", + side = self.w_mask_side, + index = pin_bit_index, + pin_list = pin_list, + ) + used_wmask_pins += pin_used + pin_bit_index += 1 + return + + def generate_ports(self): + for side in self.sides: + if side == 'left': # pin creation on sides bottom up + self.write_rport_control_pin() + self.write_rport_addr_in_pin() + if side == 'top': + self.write_rport_rd_out_pin() + if side == 'right': + self.write_wport_control_pin() + self.write_wport_addr_in_pin() + self.write_wmask_in_pin() + if side == 'bottom': + self.write_wport_wd_in_pin() + + self.debug_check_ports = True + if self.debug_check_ports: + with open("validate_ports.txt", 'w') as f: + for key,value in self.ports_written.items(): + print(f'key,value: {key} {value}' + , file=f + ) + + diff --git a/scripts/utils/gen_lef/logic/lef_writers.py b/scripts/utils/gen_lef/logic/lef_writers.py new file mode 100644 index 0000000..6b06077 --- /dev/null +++ b/scripts/utils/gen_lef/logic/lef_writers.py @@ -0,0 +1,250 @@ +from utils.gen_lef.modules.class_lefparameters import LEF_Parameters +from utils.gen_lef.lef_globals import * +from utils.gen_lef.decimal_helpers import * + +################################################################################ +# LEF WRITE FUNCTION HELPER CLASS +# +# Utilities for generating a complete LEF macro for FakeRAM 2.0: header, +# power-grid straps, OBS blockage, and pin rectangles (I/O; vertical/horizontal). +# This class relies on tech/process and geometry set in LEF_Parameters (e.g., metal +# prefix, layer indices, widths/heights, manufacturing grid, file path). +# +# Functions: +# gen_header() - write LEF header, properties, open MACRO +# gen_straps() - write VSS/VDD power grid pins +# gen_obs() - write OBS blockage, optional OVERLAP, +# close MACRO and LIBRARY +# write_input_vertical_pin_to_lef() - write INPUT pin on left/right edge +# write_output_vertical_pin_to_lef() - write OUTPUT pin on left/right edge +# write_input_horizontal_pin_to_lef() - write INPUT pin on top/bottom edge +# write_output_horizontal_pin_to_lef() - write OUTPUT pin on top/bottom edge +################################################################################ + +class LEF_WriteFunctions(LEF_Parameters): + def __init__(self, mem): + super().__init__(mem) + + ### Write header ### + def gen_header(self) -> None: + with open(self.LEF_file, 'w') as lef_file: + lef_file.write('# Generated by OpenFakeRAM\n') + lef_file.write('VERSION 5.7 ;\n') + lef_file.write('BUSBITCHARS "[]" ;\n') + lef_file.write('PROPERTYDEFINITIONS\n') + lef_file.write(' MACRO width INTEGER ;\n') + lef_file.write(' MACRO depth INTEGER ;\n') + lef_file.write(' MACRO banks INTEGER ;\n') + lef_file.write('END PROPERTYDEFINITIONS\n') + lef_file.write('MACRO %s\n' % (self.name)) + lef_file.write(f' PROPERTY width {self.bits} ;\n') + lef_file.write(f' PROPERTY depth {self.depth} ;\n') + lef_file.write(f' PROPERTY banks {self.banks} ;\n') + lef_file.write(' FOREIGN %s 0 0 ;\n' % (self.name)) + lef_file.write(' SYMMETRY X Y R90 ;\n') + lef_file.write(' SIZE %.3f BY %.3f ;\n' % (self.w, self.mem.height_um)) + lef_file.write(' CLASS BLOCK ;\n') + + ### Write obs ### + def gen_obs(self) -> None: + metalLayerPins = str(max(self.metLayerHorizontalPin, + self.metLayerVerticalPin, + self.metLayerPowerGrid)) + + with open(self.LEF_file, 'a') as lef_file: + lef_file.write(' OBS\n') + + # full rect + pin_layer_number = metalLayerPins.replace(self.metal_prefix, "", 1).strip('"') + for x in range(int(pin_layer_number)): + dummy = x + 1 + lef_file.write(' LAYER %s%d ;\n' % (self.metal_prefix, dummy)) + lef_file.write(' RECT 0 0 %.3f %.3f ;\n' % (self.w, self.mem.height_um)) + + # TODO + if (self.mem.process.tech_nm != 7): + lef_file.write(' LAYER OVERLAP ;\n') + lef_file.write(' RECT 0 0 %.3f %.3f ;\n' % (self.w, self.mem.height_um)) + + lef_file.write(' END\n') + lef_file.write('END %s\n' % self.name) + lef_file.write('\n') + lef_file.write('END LIBRARY\n') + + def gen_straps(self) -> None: + pin_size = max(self.x_pin_height, self.x_pin_width, self.y_pin_height, self.y_pin_width) + supply_pin_width = self.powerGridWidth_um + supply_pin_half_width = snap_to_grid(supply_pin_width/2, self.manufacturing_grid_um) + supply_pin_pitch = snap_to_grid(self.powerGridPitch_um, self.manufacturing_grid_um) + + with open(self.LEF_file, 'a') as lef_file: + if self.directionPowerGrid == 'vertical': + # Prevent overlapped from VDD/VSS by adding pin width + x_offset + pw_space = snap_to_grid(pin_size*2, self.manufacturing_grid_um) + x_step = snap_to_grid(pw_space + supply_pin_pitch*2, self.manufacturing_grid_um) + self.powerGridOffset_um + + # Write VSS pins + lef_file.write(' PIN VSS\n') + lef_file.write(' DIRECTION INOUT ;\n') + lef_file.write(' USE GROUND ;\n') + lef_file.write(' PORT\n') + lef_file.write(' LAYER %s%s ;\n' % (self.metal_prefix, self.metLayerPowerGrid)) + + while x_step <= self.w - (pin_size*2): + lef_file.write(' RECT %.3f %.3f %.3f %.3f ;\n' % + (x_step-supply_pin_half_width, (pin_size*2), + x_step+supply_pin_half_width, self.mem.height_um-(pin_size*2))) + x_step = snap_to_grid(x_step + supply_pin_pitch*2, self.manufacturing_grid_um) + + lef_file.write(' END\n') + lef_file.write(' END VSS\n') + + # Write VDD pins + x_step = snap_to_grid(pw_space + supply_pin_pitch, self.manufacturing_grid_um) + self.powerGridOffset_um + lef_file.write(' PIN VDD\n') + lef_file.write(' DIRECTION INOUT ;\n') + lef_file.write(' USE POWER ;\n') + lef_file.write(' PORT\n') + lef_file.write(' LAYER %s%s ;\n' % (self.metal_prefix, self.metLayerPowerGrid)) + + while x_step <= self.w - (pin_size*2): + lef_file.write(' RECT %.3f %.3f %.3f %.3f ;\n' % + (x_step-supply_pin_half_width, (pin_size*2), + x_step+supply_pin_half_width, self.mem.height_um-(pin_size*2))) + x_step = snap_to_grid(x_step + supply_pin_pitch*2, self.manufacturing_grid_um) + + lef_file.write(' END\n') + lef_file.write(' END VDD\n') + + else: # horizontal power grid + ph_space = snap_to_grid(pin_size*2, self.manufacturing_grid_um) + y_step = snap_to_grid(ph_space + supply_pin_pitch*2, self.manufacturing_grid_um) + self.powerGridOffset_um + + # Write VSS pins + lef_file.write(' PIN VSS\n') + lef_file.write(' DIRECTION INOUT ;\n') + lef_file.write(' USE GROUND ;\n') + lef_file.write(' PORT\n') + lef_file.write(' LAYER %s%s ;\n' % (self.metal_prefix, self.metLayerPowerGrid)) + + while y_step <= self.mem.height_um - (pin_size*2): + lef_file.write(' RECT %.3f %.3f %.3f %.3f ;\n' % + ((pin_size*2), y_step-supply_pin_half_width, + self.w-(pin_size*2), y_step+supply_pin_half_width)) + y_step = snap_to_grid(y_step + supply_pin_pitch*2, self.manufacturing_grid_um) + + lef_file.write(' END\n') + lef_file.write(' END VSS\n') + + # Write VDD pins + y_step = snap_to_grid(ph_space + supply_pin_pitch, self.manufacturing_grid_um) + self.powerGridOffset_um + lef_file.write(' PIN VDD\n') + lef_file.write(' DIRECTION INOUT ;\n') + lef_file.write(' USE POWER ;\n') + lef_file.write(' PORT\n') + lef_file.write(' LAYER %s%s ;\n' % (self.metal_prefix, self.metLayerPowerGrid)) + + while y_step <= self.mem.height_um - (pin_size*2): + lef_file.write(' RECT %.3f %.3f %.3f %.3f ;\n' % + ((pin_size*2), y_step-supply_pin_half_width, + self.w-(pin_size*2), y_step+supply_pin_half_width)) + y_step = snap_to_grid(y_step + supply_pin_pitch*2, self.manufacturing_grid_um) + + lef_file.write(' END\n') + lef_file.write(' END VDD\n') + + def write_input_vertical_pin_to_lef(self, pinslot: object, pin_id, side) -> None: + y_start = pinslot.slot + y_top = d_get_add(y_start, self.y_pin_width) + + with open(self.LEF_file, 'a') as lef_file: + lef_file.write(f' PIN {pin_id}\n') + lef_file.write(f' DIRECTION INPUT ;\n') + lef_file.write(' USE SIGNAL ;\n') + lef_file.write(' SHAPE ABUTMENT ;\n') + lef_file.write(' PORT\n') + lef_file.write(f' LAYER {self.metal_prefix}{pinslot.metLayer} ;\n') + + if side == 'left': + lef_file.write(' RECT %.3f %.3f %.3f %.3f ;\n' % + (0.0, y_start, self.y_pin_height, y_top)) + elif side == 'right': + lef_file.write(' RECT %.3f %.3f %.3f %.3f ;\n' % + (self.w - self.y_pin_height, y_start, self.w, y_top)) + + lef_file.write(' END\n') + lef_file.write(f' END {pin_id}\n') + + def write_output_vertical_pin_to_lef(self, pinslot: object, pin_id, side) -> None: + y_start = pinslot.slot + y_top = d_get_add(y_start, self.y_pin_height) + + with open(self.LEF_file, 'a') as lef_file: + lef_file.write(f' PIN {pin_id}\n') + lef_file.write(f' DIRECTION OUTPUT ;\n') + lef_file.write(' USE SIGNAL ;\n') + lef_file.write(' SHAPE ABUTMENT ;\n') + lef_file.write(' PORT\n') + lef_file.write(f' LAYER {self.metal_prefix}{pinslot.metLayer} ;\n') + + if side == 'left': + lef_file.write(' RECT %.3f %.3f %.3f %.3f ;\n' % + (0.0, y_start, self.y_pin_width, y_top)) + elif side == 'right': + lef_file.write(' RECT %.3f %.3f %.3f %.3f ;\n' % + (self.w - self.y_pin_width, y_start, self.w, y_top)) + + lef_file.write(' END\n') + lef_file.write(f' END {pin_id}\n') + + def write_input_horizontal_pin_to_lef(self, pinslot: object, pin_id, side) -> None: + x_start = pinslot.slot + x_right = x_start + self.x_pin_width + + with open(self.LEF_file, 'a') as lef_file: + lef_file.write(f' PIN {pin_id}\n') + lef_file.write(f' DIRECTION INPUT ;\n') + lef_file.write(' USE SIGNAL ;\n') + lef_file.write(' SHAPE ABUTMENT ;\n') + lef_file.write(' PORT\n') + lef_file.write(f' LAYER {self.metal_prefix}{pinslot.metLayer} ;\n') + + if side == 'top': + lef_file.write(' RECT %.3f %.3f %.3f %.3f ;\n' % + (x_start, self.mem.height_um - self.x_pin_height, x_right, self.mem.height_um)) + elif side == 'bottom': + lef_file.write(' RECT %.3f %.3f %.3f %.3f ;\n' % + (x_start, 0.0, x_right, self.x_pin_height)) + + lef_file.write(' END\n') + lef_file.write(f' END {pin_id}\n') + + def write_output_horizontal_pin_to_lef(self, pinslot: object, pin_id, side) -> None: + x_start = pinslot.slot + x_right = x_start + self.x_pin_width + + with open(self.LEF_file, 'a') as lef_file: + lef_file.write(f' PIN {pin_id}\n') + lef_file.write(f' DIRECTION OUTPUT ;\n') + lef_file.write(' USE SIGNAL ;\n') + lef_file.write(' SHAPE ABUTMENT ;\n') + lef_file.write(' PORT\n') + lef_file.write(f' LAYER {self.metal_prefix}{pinslot.metLayer} ;\n') + + if side == 'top': + lef_file.write(' RECT %.3f %.3f %.3f %.3f ;\n' % + (x_start, self.mem.height_um - self.x_pin_height, x_right, self.mem.height_um)) + elif side == 'bottom': + lef_file.write(' RECT %.3f %.3f %.3f %.3f ;\n' % + (x_start, 0.0, x_right, self.x_pin_height)) + + lef_file.write(' END\n') + lef_file.write(f' END {pin_id}\n') + + # def main(self): + # self.gen_header() + # self.gen_straps() + # self.gen_obs() + +# if __name__ == "__main__": +# main() \ No newline at end of file diff --git a/scripts/utils/gen_lef/logic/pin_wrappers.py b/scripts/utils/gen_lef/logic/pin_wrappers.py new file mode 100644 index 0000000..deac120 --- /dev/null +++ b/scripts/utils/gen_lef/logic/pin_wrappers.py @@ -0,0 +1,354 @@ + +import sys +import traceback + +from utils.gen_lef.lef_globals import * +from utils.gen_lef.logic.lef_writers import * +from utils.gen_lef.logic.gen_ports import * + + +################################################################################ +# WRAPPER CLASSES +# +# PinListWrapper: holds tuned placement parameters and helpers that return +# prevalidated pin-slot lists for specific sections/sides. +# +# PinIndexWrapper: writes pins by index into a given pin_list with safety checks. +################################################################################ + +class PinListWrapper(LEF_Parameters): + def __init__(self, mem): + super().__init__(mem) + self.mem = mem + self.left_group_padding = 0.005 + self.right_group_padding = 0.005 + self.w_mask_padding = 0.01 + self.r_port_control_padding = self.left_group_padding + self.r_addr_padding = self.left_group_padding + self.w_port_control_padding = self.right_group_padding + self.w_addr_padding = self.right_group_padding + self.min_distance_between_port_pins = 5 + self.r_control_metal_layer = self.metLayerVerticalPin + self.r_control_section_start = 0.03 + self.r_control_section_end = 0.04 + self.r_control_metal_layer = self.metLayerVerticalPin + self.r_control_side = "left" + self.w_control_metal_layer = self.metLayerVerticalPin + self.w_control_side = "right" + self.w_control_section_start = 0.90 + self.w_control_section_end = 0.92 + self.r_addr_in_metal_layer = self.metLayerVerticalPin + self.r_addr_in_side = "left" + self.r_addr_in_section_start = 0.15 + self.r_addr_in_section_end = 0.25 + self.w_addr_in_metal_layer = self.metLayerVerticalPin + self.w_addr_in_side = "right" + self.w_addr_in_section_start = 0.15 + self.w_addr_in_section_end = 0.25 + self.w_mask_side = "right" + self.w_mask_section_start = 0.02 + self.w_mask_section_end = 0.08 + self.w_mask_metal_layer = self.metLayerVerticalPin + self.rd_out_pin_margin = 0.05 + self.rd_out_metal_layer = self.metLayerHorizontalPin + self.rd_out_side = "top" + self.wd_in_pin_margin = 0.05 + self.wd_in_metal_layer = self.metLayerHorizontalPin + self.wd_in_side = 'bottom' + + self.is_r_port_and_rw_port = True if (self.num_rports > 0 and self.num_rwports > 0) else False + self.is_r_port_or_rw_port = True if (self.num_rports > 0 or self.num_rwports > 0) else False + self.is_r_port_or_w_port = True if (self.num_rports > 0 or self.num_rwports > 0 or self.num_wports > 0) else False + self.is_r_port_and_w_port = True if (self.num_rports > 0 and self.num_wports > 0) or self.num_rwports > 0 else False + +#### Private Functions +#--------------------- + def _validate_generated_pin_list(self + , pin_list : list + , num_pins : int + , start : float = None + , end : float = None ) -> None: + if len(pin_list) < num_pins: + traceback.print_stack() + if start == None or end == None: + print(f'ERROR: Not enough valid pins in section {start}-{end}') + print(f'ERROR: Got {len(pin_list)} pins, needed {num_pins}') + raise SystemExit("Exiting with error") + return + +#### Public Functions +#-------------------- + def get_list_sectioned_r_addr_pins_wrapper(self + , curr_port_num : int ) -> list: + """ Wrapper for function `write_rport_addr_in_pin` + Contains special variables `track_rw_port_addr_in` to track how large the read port side is. + If there are read and read-write ports in the given SRAM, function `write_rport_addr_in_pin` + deletes 'rw' key from local dictionary, thus marking `is_rw_port_addr_in_written` variable to False. + """ + self.r_addr_padding = self.left_group_padding + section_start, section_end = self.r_addr_in_section_start, self.r_addr_in_section_end + section_size = d_get_subtract_round_second_fpoint(section_end, section_start) + current_section_start = d_get_add_round_second_fpoint(section_start + , d_get_multiply(curr_port_num, section_size + ) + ) + current_section_end = d_get_add_round_second_fpoint(current_section_start, section_size) + if curr_port_num > 0: + if self.r_addr_padding == None: + print("INFO: No padding specified, using default.") + pin_list = self.generate_equidistant_sectioned_pins( + side = self.r_addr_in_side, + start = current_section_start, + end = current_section_end, + metLayer = self.r_addr_in_metal_layer, + num_pins = self.addr_width, + padding = self.r_addr_padding, + new_section = True + ) + self.r_addr_padding += self.left_group_padding + else: + pin_list = self.generate_equidistant_sectioned_pins( + side = self.r_addr_in_side, + start = current_section_start, + end = current_section_end, + metLayer = self.r_addr_in_metal_layer, + num_pins = self.addr_width + ) + self._validate_generated_pin_list(pin_list + , self.addr_width + , start = current_section_start + , end = current_section_end + ) + return pin_list + + def get_list_sectioned_r_control_pins_wrapper(self + , curr_port_num : int + , r_num_control_pins : int ) -> list: + section_start, section_end = self.r_control_section_start, self.r_control_section_end + section_size = d_get_subtract_round_second_fpoint(section_end, section_start) + + current_section_start = d_get_add_round_second_fpoint(section_start + , d_get_multiply(curr_port_num, section_size + ) + ) + current_section_end = d_get_add_round_second_fpoint(current_section_start, section_size) + + if curr_port_num > 0: + if self.r_port_control_padding == None: + print("INFO: No padding specified, using default.") + pin_list = self.generate_equidistant_sectioned_pins( + side = self.r_control_side, + start = current_section_start, + end = current_section_end, + metLayer = self.r_control_metal_layer, + num_pins = r_num_control_pins, + padding = self.r_port_control_padding, + new_section = True + ) + self.r_port_control_padding += self.left_group_padding + else: + pin_list = self.generate_equidistant_sectioned_pins( + side = self.r_control_side, + start = current_section_start, + end = current_section_end, + metLayer = self.r_control_metal_layer, + num_pins = r_num_control_pins + ) + self._validate_generated_pin_list(pin_list + , r_num_control_pins + , start = current_section_start + , end = current_section_start + ) + return pin_list + + def get_list_sectioned_w_addr_pins_wrapper(self + , curr_port_num : int) -> list: + """ Wrapper for function `write_wport_addr_in_pin` + + Handles special case where generated list from `generate_equidistant_sectioned_pins` + returns a list of pin slots with PinSlot.side = 'left', only if left side is free. + """ + section_start, section_end = self.w_addr_in_section_start, self.w_addr_in_section_end + section_size = d_get_subtract_round_second_fpoint(section_end, section_start) + self.track_w_port_end = section_start + + current_section_start = d_get_add_round_second_fpoint(section_start, d_get_multiply(curr_port_num, section_size)) + current_section_end = d_get_add_round_second_fpoint(current_section_start, section_size) + if curr_port_num > 0: + if self.w_addr_padding == None: + print("INFO: No padding specified, using default.") + pin_list = self.generate_equidistant_sectioned_pins( + side = self.w_addr_in_side, + start = current_section_start, + end = current_section_end, + metLayer = self.w_addr_in_metal_layer, + num_pins = self.addr_width, + padding = self.w_addr_padding, + new_section = True + ) + self.w_addr_padding += self.left_group_padding + else: + pin_list = self.generate_equidistant_sectioned_pins( + side = self.w_addr_in_side, + start = current_section_start, + end = current_section_end, + metLayer = self.w_addr_in_metal_layer, + num_pins = self.addr_width + ) + self._validate_generated_pin_list(pin_list + , self.addr_width + , start = current_section_start + , end = current_section_end) + return pin_list + + def get_list_sectioned_w_control_pins_wrapper(self + , curr_port_num : int + , w_num_control_pins : int) -> list: + section_start, section_end = self.w_control_section_start, self.w_control_section_end + section_size = d_get_subtract_round_second_fpoint(section_end, section_start) + current_section_start = d_get_add_round_second_fpoint(section_start, d_get_multiply(curr_port_num, section_size)) + current_section_end = d_get_add_round_second_fpoint(current_section_start, section_size) + if curr_port_num > 0: + if self.w_port_control_padding == None: + print("INFO: No padding specified, using default.") + pin_list = self.generate_equidistant_sectioned_pins( + side = self.w_control_side, + start = current_section_start, + end = current_section_end, + metLayer = self.w_control_metal_layer, + num_pins = w_num_control_pins, + padding = self.w_port_control_padding, + new_section = True + ) + self.w_port_control_padding += self.left_group_padding + + else: + pin_list = self.generate_equidistant_sectioned_pins( + side = self.w_control_side, + start = current_section_start, + end = current_section_end, + metLayer = self.w_control_metal_layer, + num_pins = w_num_control_pins + ) + self._validate_generated_pin_list(pin_list + , w_num_control_pins + , start = current_section_start + , end = current_section_end) + return pin_list + + def get_list_sectioned_wmask_pins_wrapper(self + , curr_port_num : int + , total_wmask_in_bits) -> list: + if curr_port_num > 0: + if self.w_mask_padding == None: + print("INFO: No padding specified, using default.") + pin_list = self.generate_equidistant_sectioned_pins( + side = self.w_mask_side, + start = self.w_mask_section_start, + end = self.w_mask_section_end, + metLayer = self.w_mask_metal_layer, + num_pins = total_wmask_in_bits, + padding = self.w_mask_padding, + new_section = True + ) + else: + pin_list = self.generate_equidistant_sectioned_pins( + side = self.w_mask_side, + start = self.w_mask_section_start, + end = self.w_mask_section_end, + metLayer = self.w_mask_metal_layer, + num_pins = total_wmask_in_bits + ) + self._validate_generated_pin_list(pin_list + , total_wmask_in_bits + , start = self.w_mask_section_start + , end = self.w_mask_section_end) + return pin_list + + def get_equidistant_whole_side_rd_pins_wrapper(self + , side : str + , metalLayer : int + , num_pins : int) -> list: + pin_list = self.get_equidistant_whole_side_pins(side + , metalLayer + , margin=self.rd_out_pin_margin + , num_pins=num_pins + , min_distance=self.min_distance_between_port_pins) + + self._validate_generated_pin_list(pin_list, num_pins) + return pin_list + + def get_equidistant_whole_side_wd_pins_wrapper(self + , side : str + , metalLayer : int + , num_pins : int) -> list: + pin_list = self.get_equidistant_whole_side_pins(side + , metalLayer + , margin=self.wd_in_pin_margin + , num_pins=num_pins + , min_distance=self.min_distance_between_port_pins) + self._validate_generated_pin_list(pin_list, num_pins) + return pin_list + +class PinIndexWrapper(LEF_WriteFunctions): + def __init__(self, mem): + super().__init__(mem) + +#### Private Functions +#--------------------- + def _validate_index_less_than_pin_list(self + , pin_id : str + , index : int + , pin_list : list[object] ) -> None: + if index >= len(pin_list): + traceback.print_stack() + print(f'ERROR: {pin_id}: {pin_id} index {index} out of bounds (pin_list has {len(pin_list)} pins)') + raise SystemExit("Exiting with error") + return + +#### Public Functions +#-------------------- + def get_index_write_vertical_input_wrapper(self + , pin_id : str + , side : str + , index : int + , pin_list : list) -> int: + + self._validate_index_less_than_pin_list(pin_id, index, pin_list) + + self.write_input_vertical_pin_to_lef( + pinslot = pin_list[index], + pin_id = pin_id, + side = side + ) + return 1 + + def get_index_write_horizontal_output_wrapper(self + , pin_id : str + , side : str + , index : int + , pin_list : list) -> int: + + self._validate_index_less_than_pin_list(pin_id, index, pin_list) + + self.write_output_horizontal_pin_to_lef( + pinslot = pin_list[index], + pin_id = pin_id, + side = side + ) + return 1 + + def get_index_write_horizontal_input_wrapper(self + , pin_id : str + , side : str + , index : int + , pin_list : list) -> int: + + self._validate_index_less_than_pin_list(pin_id, index, pin_list) + + self.write_input_horizontal_pin_to_lef( + pinslot = pin_list[index], + pin_id = pin_id, + side = side + ) + return 1 \ No newline at end of file diff --git a/scripts/utils/gen_lef/modules/__init__.py b/scripts/utils/gen_lef/modules/__init__.py new file mode 100644 index 0000000..574710f --- /dev/null +++ b/scripts/utils/gen_lef/modules/__init__.py @@ -0,0 +1,2 @@ +from utils.gen_lef.modules.class_lefparameters import * +from utils.gen_lef.modules.class_pingrid import * \ No newline at end of file diff --git a/scripts/utils/gen_lef/modules/class_lefparameters.py b/scripts/utils/gen_lef/modules/class_lefparameters.py new file mode 100644 index 0000000..75a7a6d --- /dev/null +++ b/scripts/utils/gen_lef/modules/class_lefparameters.py @@ -0,0 +1,72 @@ +import os + +from utils.gen_lef.lef_globals import * +from utils.gen_lef.coordinates.snap_height import snap_height_to_track + +from decimal import Decimal, ROUND_HALF_UP + +################################################################################ +# LEF PARAMETERS CLASS +# +# Centralizes process/memory geometry and tech knobs for LEF generation: +# sets paths, macro dimensions, pin pitches, metal layers, and power grid. +# Computes derived pitches, snaps width/height to manufacturing grid, and +# snaps height to track when enabled. +# +# Functions: +# _d_get_multiply_value_round_halfup() - multiply and round (half-up) to a given increment +################################################################################ + +class LEF_Parameters: + def __init__(self, mem): + self.mem = mem + self.LEF_file = os.sep.join([mem.results_dir, mem.fakeram_name_extension + mem.name + '.lef']) + self.name = mem.name + self.depth = mem.depth + self.bits = mem.width_in_bits + self.addr_width = mem.addr_width + self.metal_prefix = mem.process.metalPrefix + self.num_rports = mem.r + self.num_wports = mem.w + self.num_rwports = mem.rw + self.num_wmasks = mem.wmask + self.has_wmask = mem.has_write_mask + self.banks = mem.num_banks + self.metLayerHorizontalPin = mem.process.metLayerHorizontalPin + self.metLayerVerticalPin = mem.process.metLayerVerticalPin + self.x_offset = mem.process.x_pinOffset_um + self.y_offset = mem.process.y_pinOffset_um + self.pin_pitch_factor = mem.pinPitchFactor + self.unscaled_x_pin_pitch = mem.process.x_pinPitch_um + self.unscaled_y_pin_pitch = mem.process.pinPitch_um + self.x_pin_width = mem.process.x_pinWidth_um + self.x_pin_height = mem.process.x_pinHeight_um + self.y_pin_width = mem.process.y_pinWidth_um + self.y_pin_height = mem.process.y_pinHeight_um + self.metLayerPowerGrid = mem.process.metLayerPowerGrid + self.directionPowerGrid = mem.process.directionPowerGrid + self.powerGridWidth_um = mem.process.powerGridWidth_um + self.powerGridPitch_um = mem.process.powerGridPitch_um + self.powerGridOffset_um = mem.process.powerGridOffset_um + self.heightSnapPinPitch = mem.process.heightSnapPinPitch + self.widthSnapPinPitch = mem.process.widthSnapPinPitch + + self.x_pin_pitch = self._d_get_multiply_value_round_halfup(self.unscaled_x_pin_pitch, self.pin_pitch_factor, rounding_val=0.001) + self.y_pin_pitch = self._d_get_multiply_value_round_halfup(self.unscaled_y_pin_pitch, self.pin_pitch_factor, rounding_val=0.001) + + self.manufacturing_grid_um = mem.process.manufacturing_grid_um + + self.w = snap_to_grid(mem.width_um, mem.process.manufacturing_grid_um) + self.h = snap_to_grid(mem.height_um, mem.process.manufacturing_grid_um) + mem.width_um = self.w + mem.height_um = self.h + + # Calculate pin height, + # if heightSnaptoTrack true, will snap to track + # else force with y offset + self.h = snap_height_to_track(mem, self.h, self.y_pin_pitch) + mem.height_um = self.h + + def _d_get_multiply_value_round_halfup(self, value1, value2, rounding_val): + """ returns the value multiplied by pitch_factor, rounded to the nearest 0.001 (three decimal places), as a float.""" + return float((Decimal(str(value1)) * Decimal(str(value2))).quantize(Decimal(str(rounding_val)), rounding=ROUND_HALF_UP)) diff --git a/scripts/utils/gen_lef/modules/class_pingrid.py b/scripts/utils/gen_lef/modules/class_pingrid.py new file mode 100644 index 0000000..9cc1bf9 --- /dev/null +++ b/scripts/utils/gen_lef/modules/class_pingrid.py @@ -0,0 +1,198 @@ +import sys + +from utils.gen_lef.decimal_helpers import * +from utils.gen_lef.modules.class_lefparameters import LEF_Parameters + +from dataclasses import dataclass, field +from typing import List + +################################################################################ +# LEF PIN GRID HELPER +# +# Builds and validates per-edge pin-slot grids (top/bottom/left/right) for a macro. +# +# Dataclass: +# PinSlot - slot metadata (coord, side, layer, used) +# +# Functions: +# _initialize_pins() - compute offsets, seeds, and containers +# _initialize_pinlist() - populate candidate slots for all sides +# _available_slots() - append per-layer slots for a side pair +# _validate_list() - verify grid & pitch constraints +# _d_if_divisible_manfufacturing_grid() - check slot vs manufacturing grid +# _d_if_startcoord_multiple_of_pitch() - check start coord vs pin pitch +# _parse_list_side_manufacturing_grid() - validate all slots against grid +# _parse_list_side_pin_pitch() - validate all slots against pitch +################################################################################ + +@dataclass +class PinSlot: + """ Reusable pin coord class """ + slot : float = field(default_factory=float) + used : bool = field(default_factory=bool) + side : str = field(default_factory=str) + metLayer : int = field(default_factory=object) + +class PinGrid(LEF_Parameters): + def __init__(self,mem): + super().__init__(mem) + self.list_top_pins : List[object] = None + self.list_bot_pins : List[object] = None + self.list_left_pins : List[object] = None + self.list_right_pins : List[object] = None + self.w_offset : float = None + self.h_offset : float = None + + # DEBUG PARAMS + self.debug_list_validation : bool = False + + self._initialize_pins() + self._initialize_pinlist() + self._validate_list() + self.num_gen_section_calls = 0 + +#### Private Functions +#--------------------- + def _initialize_pins(self) -> None : + self.total_layers = max(self.metLayerHorizontalPin, self.metLayerVerticalPin) + self.w_offset = d_get_subtract(self.w, self.x_offset) + self.h_offset = d_get_subtract(self.h, self.y_offset) + self.x_dummy_pin = d_get_divide(self.x_pin_width, 2) + self.y_dummy_pin = d_get_divide(self.y_pin_width, 2) + self.start_coord_rl = d_get_add(self.y_offset, self.y_pin_pitch) + self.start_coord_tb = d_get_add(self.x_offset, self.x_pin_pitch) + self.list_top_pins , self.list_bot_pins = [], [] + self.list_left_pins, self.list_right_pins = [], [] + + def _initialize_pinlist(self) -> None: + self._available_slots( + self.w, self.x_pin_pitch, self.x_offset, self.x_dummy_pin, self.x_pin_width, + self.start_coord_tb, self.list_top_pins, self.list_bot_pins, + 'top', 'bottom' + ) + + self._available_slots( + self.h, self.y_pin_pitch, self.y_offset, self.y_dummy_pin, self.y_pin_width, + self.start_coord_rl, self.list_left_pins, self.list_right_pins, + 'left', 'right' + ) + + def _available_slots(self + , dimension : float + , pin_pitch : float + , offset : float + , dummy_pin : float + , pin_width : float + , start_coord : float + , list1 : list + , list2 : list + , side1 : str + , side2 : str ) -> None: + """ Generate available pin slots along a single dimension for opposing sides of a cell. + Creates pin slot positions along either the width (for top/bottom pins) or height + (for left/right pins) of the FakeRAM. + + Note: + - Creates slots for all metal layers (1 to self.total_layers) + - Slot position is the pin's starting edge (step - pin_width/2), so when + writing to LEF file, you can simply add the full pin_width to get the + ending edge without additional calculations + - Continues until remaining space is less than dummy_pin + offset + """ + step = start_coord + while dimension - dummy_pin - offset > step: + slot = d_get_subtract(step, d_get_divide(pin_width, 2)) + + for met_num in range(1, self.total_layers+1): + list1.append(PinSlot( + slot = slot + , used = False + , side = side1 + , metLayer = met_num + ) + ) + list2.append(PinSlot( + slot = slot + , used = False + , side = side2 + , metLayer = met_num + ) + ) + + step = d_get_add(step, pin_pitch) + +##### Verification + def _validate_list(self) -> None: + """ Entry point for validating each slot """ + manufacturing_grid = self.manufacturing_grid_um + + list_top_pins = self._parse_list_side_manufacturing_grid(self.list_top_pins, manufacturing_grid) + list_bot_pins = self._parse_list_side_manufacturing_grid(self.list_bot_pins, manufacturing_grid) + list_right_pins = self._parse_list_side_manufacturing_grid(self.list_right_pins, manufacturing_grid) + list_left_pins = self._parse_list_side_manufacturing_grid(self.list_left_pins, manufacturing_grid) + + list_top_pins = self._parse_list_side_pin_pitch(self.list_top_pins, self.unscaled_x_pin_pitch, self.x_pin_width) + list_bot_pins = self._parse_list_side_pin_pitch(self.list_bot_pins, self.unscaled_x_pin_pitch, self.x_pin_width) + list_right_pins = self._parse_list_side_pin_pitch(self.list_right_pins, self.unscaled_y_pin_pitch, self.y_pin_width) + list_left_pins = self._parse_list_side_pin_pitch(self.list_left_pins, self.unscaled_y_pin_pitch, self.y_pin_width) + + if len(self.list_top_pins) != len(self.list_bot_pins): + print("Top and bottom sizes are not the same!") + sys.exit(1) + if len(self.list_left_pins) != len(self.list_right_pins): + print("Left and right sizes are not the same!") + sys.exit(1) + + print( + f'list_top_pins passed : {list_top_pins}\n' + f'list_bot_pins passed : {list_bot_pins}\n' + f'list_right_pins passed : {list_right_pins}\n' + f'list_left_pins passed : {list_left_pins}\n' + f'\n' + f'Pin grid pass\n' + ) + + def _d_if_divisible_manfufacturing_grid(self + , slot : float + , grid : float ) -> bool: + """ Returns true if individual slot is divisible by its manufacturing grid """ + return True if (Decimal(str(slot)) % Decimal(str(grid)) == 0) else False + + def _parse_list_side_manufacturing_grid(self + , pin_list: object + , manufacturing_grid: float ) -> bool: + """ Returns true if all pin slots are divisible by manufacturing grid """ + for pin in pin_list: + if self._d_if_divisible_manfufacturing_grid(pin.slot, manufacturing_grid) == False: + print(f'slot ({pin.slot}) not multiple of manufacturing grid ({manufacturing_grid})') + sys.exit(1) + if self.debug_list_validation: + print(pin, "pass") + return True + + def _d_if_startcoord_multiple_of_pitch(self + , start_coord : float + , pw : float + , pitch : float ) -> bool: + """ Returns true if the starting coordinate is a multiple of the pitch """ + return True if (Decimal(str(d_get_add(start_coord, d_get_divide(pw, 2)))) % Decimal(str(pitch)) == 0) else False + + def _parse_list_side_pin_pitch(self + , pin_list : object + , pin_pitch : float + , pin_width : float ) -> bool: + for pin in pin_list: + calculated_pos = d_get_add(pin.slot, d_get_divide(pin_width, 2)) + remainder = float(Decimal(str(calculated_pos)) % Decimal(str(pin_pitch))) + + if self.debug_list_validation: + print(f"Debug: pin.slot={pin.slot}, pin_width/2={pin_width/2}, calculated_pos={calculated_pos}, pin_pitch={pin_pitch}, remainder={remainder}") + + if self._d_if_startcoord_multiple_of_pitch(pin.slot, pin_width, pin_pitch) == False: + axis = "x" if pin_pitch == self.x_pin_pitch else "y" + print(f'{axis} axis : slot ({calculated_pos}) not multiple of pin pitch! ({pin_pitch})') + sys.exit(1) + + if self.debug_list_validation: + print(f"{pin.slot} start coord: pass") + return True \ No newline at end of file diff --git a/scripts/utils/generate_lef.py b/scripts/utils/generate_lef.py deleted file mode 100644 index dbd6c6c..0000000 --- a/scripts/utils/generate_lef.py +++ /dev/null @@ -1,389 +0,0 @@ -import os -import sys -import math - -################################################################################ -# GENERATE LEF VIEW -# -# Generate a .lef file based on the given SRAM. -################################################################################ - -def generate_lef( mem ): - - # File pointer - fid = open(os.sep.join([mem.results_dir, mem.name + '.lef']), 'w') - - # Memory parameters - name = mem.name - depth = mem.depth - bits = mem.width_in_bits - w = mem.width_um - h = mem.height_um - num_rwport = mem.rw_ports - num_wport = mem.w_ports - num_rport = mem.r_ports - addr_width = math.ceil(math.log2(mem.depth)) - - # Process parameters - min_pin_width = mem.process.pinWidth_um - pin_height = mem.process.pinHeight_um - min_pin_pitch = mem.process.pinPitch_um - metalPrefix = mem.process.metalPrefix - flip = mem.process.flipPins.lower() == 'true' - - rw_clks, r_clks, w_clks = mem.port_clks - unique_clks = list(set(x for sub in mem.port_clks for x in sub)) - # Offset from bottom edge to first pin - x_offset = 10 * min_pin_pitch ;# arbitrary offset (looks decent) - y_offset = 10 * min_pin_pitch ;# arbitrary offset (looks decent) - ######################################### - # Calculate the pin spacing (pitch) - ######################################### - - number_of_pins = num_wport*2*bits + num_rwport*3*bits + num_rport*2*bits + (num_wport + num_rwport + num_rport) * (addr_width + 3) - number_of_tracks_available = math.floor((h - 2*y_offset) / min_pin_pitch) - number_of_spare_tracks = number_of_tracks_available - number_of_pins - - print(f'Final {name} size = {w} x {h}') - print(f'num pins: {number_of_pins}, available tracks: {number_of_tracks_available}') - if number_of_spare_tracks < 0: - print("ERROR: not enough tracks!") - sys.exit(1) - - track_count = 1 - while number_of_spare_tracks > 0: - track_count += 1 - number_of_spare_tracks = number_of_tracks_available - number_of_pins*track_count - track_count -= 1 - - pin_pitch = min_pin_pitch * track_count - group_pitch = math.floor((number_of_tracks_available - number_of_pins*track_count) / 4)*mem.process.pinPitch_um - - ######################################### - # LEF HEADER - ######################################### - - fid.write('VERSION 5.7 ;\n') - fid.write('BUSBITCHARS "[]" ;\n') - fid.write('MACRO %s\n' % (name)) - fid.write(' FOREIGN %s 0 0 ;\n' % (name)) - fid.write(' SYMMETRY X Y R90 ;\n') - fid.write(' SIZE %.3f BY %.3f ;\n' % (w,h)) - fid.write(' CLASS BLOCK ;\n') - - ######################################## - # LEF SIGNAL PINS - ######################################## - - y_step = y_offset - for ct in range(num_rwport) : - for i in range(int(bits)) : - y_step = lef_add_pin( fid, mem, f'w_mask_rw{ct+1}[{i}]', True, y_step, pin_pitch ) - y_step += group_pitch-pin_pitch - - for ct in range(num_wport) : - for i in range(int(bits)) : - y_step = lef_add_pin( fid, mem, f'w_mask_w{ct+1}[{i}]', True, y_step, pin_pitch ) - y_step += group_pitch-pin_pitch - - for ct in range(num_rwport) : - for i in range(int(bits)) : - y_step = lef_add_pin( fid, mem, f'rd_out_rw{ct+1}[{i}]', False, y_step, pin_pitch ) - y_step += group_pitch-pin_pitch - - for ct in range(num_rport): - for i in range(int(bits)) : - y_step = lef_add_pin( fid, mem, f'rd_out_r{ct+1}[{i}]', False, y_step, pin_pitch ) - y_step += group_pitch-pin_pitch - - for ct in range(num_rwport) : - for i in range(int(bits)) : - y_step = lef_add_pin( fid, mem, f'wd_in_rw{ct+1}[{i}]', True, y_step, pin_pitch ) - y_step += group_pitch-pin_pitch - - for ct in range(num_wport) : - for i in range(int(bits)) : - y_step = lef_add_pin( fid, mem, f'wd_in_w{ct+1}[{i}]', True, y_step, pin_pitch ) - y_step += group_pitch-pin_pitch - - for ct in range(num_rwport) : - for i in range(int(addr_width)) : - y_step = lef_add_pin( fid, mem, f'addr_rw{ct+1}[{i}]', True, y_step, pin_pitch ) - y_step += group_pitch-pin_pitch - - for ct in range(num_wport) : - for i in range(int(addr_width)) : - y_step = lef_add_pin( fid, mem, f'addr_w{ct+1}[{i}]', True, y_step, pin_pitch ) - y_step += group_pitch-pin_pitch - - for ct in range(num_rport) : - for i in range(int(addr_width)) : - y_step = lef_add_pin( fid, mem, f'addr_r{ct+1}[{i}]', True, y_step, pin_pitch ) - y_step += group_pitch-pin_pitch - - for ct in range(num_rwport) : - y_step = lef_add_pin( fid, mem, f'we_in_rw{ct+1}', True, y_step, pin_pitch ) - for ct in range(num_wport) : - y_step = lef_add_pin( fid, mem, f'we_in_w{ct+1}', True, y_step, pin_pitch ) - for ct in range(num_rwport) : - y_step = lef_add_pin( fid, mem, f'ce_rw{ct+1}', True, y_step, pin_pitch) - for ct in range(num_wport): - y_step = lef_add_pin( fid, mem, f'ce_w{ct+1}', True, y_step, pin_pitch ) - for ct in range(num_rport): - y_step = lef_add_pin( fid, mem, f'ce_r{ct+1}', True, y_step, pin_pitch ) - for i in unique_clks: - num = '' if (len(unique_clks) == 1) else i - y_step = lef_add_pin( fid, mem, f'clk{num}', True, y_step, pin_pitch ) - - ######################################## - # Create VDD/VSS Strapes - ######################################## - - supply_pin_width = min_pin_width*4 - supply_pin_half_width = supply_pin_width/2 - supply_pin_pitch = min_pin_pitch*8 - supply_pin_layer = '%s4' % metalPrefix - - - fid.write(' PIN VSS\n') - fid.write(' DIRECTION INOUT ;\n') - fid.write(' USE GROUND ;\n') - fid.write(' PORT\n') - fid.write(' LAYER %s ;\n' % supply_pin_layer) - - if flip: # Vertical straps - x_step = x_offset - while x_step <= w - x_offset: - fid.write(' RECT %.3f %.3f %.3f %.3f ;\n' % (x_step-supply_pin_half_width, y_offset, x_step+supply_pin_half_width, h-y_offset)) - x_step += supply_pin_pitch*2 - else: # Horizontal straps - y_step = y_offset - while y_step <= h - y_offset: - fid.write(' RECT %.3f %.3f %.3f %.3f ;\n' % (x_offset, y_step-supply_pin_half_width, w-x_offset, y_step+supply_pin_half_width)) - y_step += supply_pin_pitch*2 - fid.write(' END\n') - fid.write(' END VSS\n') - - fid.write(' PIN VDD\n') - fid.write(' DIRECTION INOUT ;\n') - fid.write(' USE POWER ;\n') - fid.write(' PORT\n') - fid.write(' LAYER %s ;\n' % supply_pin_layer) - if flip: # Vertical straps - x_step = x_offset + supply_pin_pitch - while x_step <= w - x_offset: - fid.write(' RECT %.3f %.3f %.3f %.3f ;\n' % (x_step-supply_pin_half_width, y_offset, x_step+supply_pin_half_width, h-y_offset)) - x_step += supply_pin_pitch*2 - else: # Horizontal straps - y_step = y_offset + supply_pin_pitch - while y_step <= h - y_offset: - fid.write(' RECT %.3f %.3f %.3f %.3f ;\n' % (x_offset, y_step-supply_pin_half_width, w-x_offset, y_step+supply_pin_half_width)) - y_step += supply_pin_pitch*2 - fid.write(' END\n') - fid.write(' END VDD\n') - - # Horizontal straps - - ######################################## - # Create obstructions - ######################################## - - fid.write(' OBS\n') - - ################ - # Layer 1 - ################ - - # No pins (full rect) - fid.write(' LAYER %s1 ;\n' % metalPrefix) - fid.write(' RECT 0 0 %.3f %.3f ;\n' % (w,h)) - - ################ - # Layer 2 - ################ - - # No pins (full rect) - fid.write(' LAYER %s2 ;\n' % metalPrefix) - fid.write(' RECT 0 0 %.3f %.3f ;\n' % (w,h)) - - ################ - # Layer 3 - ################ - - fid.write(' LAYER %s3 ;\n' % metalPrefix) - - # Flipped therefore pins on M3 - if flip: - - # Rect from top to bottom, just right of pins to right edge - fid.write(' RECT %.3f 0 %.3f %.3f ;\n' % (pin_height,w,h)) - - # Walk through same calculation as pins and draw from bottom of the - # current pin to the top of last pin (start with bottom edge) - prev_y = 0 - y_step = y_offset - for ct in range(num_rwport + num_wport): # w_mask_rw & w_mask_w - for i in range(int(bits)) : - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,pin_height,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - y_step += group_pitch-pin_pitch - for ct in range(num_rwport + num_rport): # rd_out_rw & rd_out_r - for i in range(int(bits)) : - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,pin_height,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - y_step += group_pitch-pin_pitch - for ct in range(num_rwport + num_wport): # wd_in_rw & wd_in_w - for i in range(int(bits)) : - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,pin_height,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - y_step += group_pitch-pin_pitch - for ct in range(num_rwport + num_rport + num_wport): # addr_rw & addr_r & addr_w - for i in range(int(addr_width)) : - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,pin_height,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - y_step += group_pitch-pin_pitch - for ct in range(num_rwport + num_wport): # we_in_rw & we_in_w - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,pin_height,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - for ct in range(num_rwport + num_rport + num_wport): # ce_rw & ce_r & ce_w - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,pin_height,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - for ct in range(len(unique_clks)): # Single clk by default but handling for multiple clk pins - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,pin_height,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - # Final shapre from top of last pin to top edge - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,pin_height,h)) - - # Not flipped therefore no pins on M3 (Full rect) - else: - fid.write(' RECT 0 0 %.3f %.3f ;\n' % (w,h)) - - ################ - # Layer 4 - ################ - - fid.write(' LAYER %s4 ;\n' % metalPrefix) - - # Flipped therefore only vertical pg straps - if flip: - - # Block under and above the vertical power straps (full width) - fid.write(' RECT 0 0 %.3f %.3f ;\n' % (w, y_offset)) - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (h-y_offset,w,h)) - - # Walk through the same calculations to create pg straps and create obs - # from the left of the current strap to the right of the previous strap - # (start with the left edge) - prev_x = 0 - x_step = x_offset - while x_step <= w - x_offset: - fid.write(' RECT %.3f %.3f %.3f %.3f ;\n' % (prev_x,y_offset,x_step-supply_pin_half_width,h-y_offset)) - prev_x = x_step+supply_pin_half_width - x_step += supply_pin_pitch - - # Create a block from the right of the last strap to the right edge - fid.write(' RECT %.3f %.3f %.3f %.3f ;\n' % (prev_x,y_offset,w,h-y_offset)) - - # Not flipped therefore pins on M4 and horizontal pg straps - else: - - # Block from right of pins to left of straps and a block to the right - # of the straps (full height) - fid.write(' RECT %.3f 0 %.3f %.3f ;\n' % (min_pin_width, x_offset, h)) - fid.write(' RECT %.3f 0 %.3f %.3f ;\n' % (w-x_offset, w, h)) - - # Walk through the same calculations to create pg straps and create obs - # from the bottom of the current strap to the top of the previous strap - # (start with the bottom edge) - prev_y = 0 - y_step = y_offset - while y_step <= h - y_offset: - fid.write(' RECT %.3f %.3f %.3f %.3f ;\n' % (x_offset, prev_y, w-x_offset, y_step-supply_pin_half_width)) - prev_y = y_step+supply_pin_half_width - y_step += supply_pin_pitch - - # Create a block from the top of the last strap to the top edge - fid.write(' RECT %.3f %.3f %.3f %.3f ;\n' % (x_offset, prev_y, w-x_offset, h)) - - # Walk through same calculation as pins and draw from bottom of the - # current pin to the top of last pin (start with bottom edge) - prev_y = 0 - y_step = y_offset - for ct in range(num_rwport + num_wport): # w_mask_rw & w_mask_w - for i in range(int(bits)) : - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,min_pin_width,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - y_step += group_pitch-pin_pitch - for ct in range(num_rwport + num_rport): # rd_out_rw & rd_out_r - for i in range(int(bits)) : - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,min_pin_width,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - y_step += group_pitch-pin_pitch - for ct in range(num_rwport + num_wport): # wd_in_rw & wd_in_w - for i in range(int(bits)) : - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,min_pin_width,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - y_step += group_pitch-pin_pitch - for ct in range(num_rwport + num_rport + num_wport): # addr_rw & addr_r & addr_w - for i in range(int(addr_width)) : - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,min_pin_width,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - y_step += group_pitch-pin_pitch - for ct in range(num_rwport + num_wport): # we_in_rw & we_in_w - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,pin_height,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - for ct in range(num_rwport + num_rport + num_wport): # ce_rw & ce_r & ce_w - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,pin_height,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - for ct in range(len(unique_clks)): # Single clk by default but handling for multiple clk pins - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,pin_height,y_step-min_pin_width/2)) - prev_y = y_step+min_pin_width/2 - y_step += pin_pitch - # Final shapre from top of last pin to top edge - fid.write(' RECT 0 %.3f %.3f %.3f ;\n' % (prev_y,min_pin_width,h)) - - # Overlap layer (full rect) - if (mem.process.tech_nm != 7): - fid.write(' LAYER OVERLAP ;\n') - fid.write(' RECT 0 0 %.3f %.3f ;\n' % (w,h)) - - # Finish up LEF file - fid.write(' END\n') - fid.write('END %s\n' % name) - fid.write('\n') - fid.write('END LIBRARY\n') - fid.close() - -# -# Helper function that adds a signal pin -# -def lef_add_pin( fid, mem, pin_name, is_input, y, pitch ): - - layer = mem.process.metalPrefix + ('3' if mem.process.flipPins.lower() == 'true' else '4') - pw = mem.process.pinWidth_um - hpw = (mem.process.pinWidth_um/2.0) # half pin width - ph = mem.process.pinHeight_um - fid.write(' PIN %s\n' % pin_name) - fid.write(' DIRECTION %s ;\n' % ('INPUT' if is_input else 'OUTPUT')) - fid.write(' USE SIGNAL ;\n') - fid.write(' SHAPE ABUTMENT ;\n') - fid.write(' PORT\n') - fid.write(' LAYER %s ;\n' % layer) - fid.write(' RECT %.3f %.3f %.3f %.3f ;\n' % (0, y-hpw, ph, y+hpw)) - fid.write(' END\n') - fid.write(' END %s\n' % pin_name) - - return y + pitch \ No newline at end of file diff --git a/scripts/utils/generate_lib.py b/scripts/utils/generate_lib.py index e807aec..43f17df 100644 --- a/scripts/utils/generate_lib.py +++ b/scripts/utils/generate_lib.py @@ -29,19 +29,16 @@ def generate_lib( mem ): min_period = float(mem.cycle_time_ns) fo4 = float(mem.fo4_ps)/1e3 - # Only support 1RW srams. At some point, expose these as well! - unique_clks = list(set(x for sub in mem.port_clks for x in sub)) - - num_rwport = int(mem.rw_ports) - num_rport = int(mem.r_ports) - num_wport = int(mem.w_ports) - rw_clks, r_clks, w_clks = mem.port_clks + # Dynamic Ports + num_rport = mem.r + num_wport = mem.w + num_rwport = mem.rw + # num_wmask = mem.wmask + has_wmask = mem.has_write_mask - write_mode = str(mem.write_mode) - byte_write = 1 if int(mem.write_granularity) == 8 else 0 # Number of bits for address - addr_width = math.ceil(math.log2(mem.depth)) - addr_width_m1 = addr_width-1 + addr_width = math.ceil(math.log2(mem.depth)) + addr_width_m1 = addr_width-1 # Get the date d = datetime.date.today() @@ -71,969 +68,33 @@ def generate_lib( mem ): # Start generating the LIB file - LIB_file = open(os.sep.join([mem.results_dir, name + '.lib']), 'w') - - LIB_file.write( 'library(%s) {\n' % name) - LIB_file.write( ' technology (cmos);\n') - LIB_file.write( ' delay_model : table_lookup;\n') - LIB_file.write( ' revision : 1.0;\n') - LIB_file.write( ' date : "%s %s";\n' % (date, current_time)) - LIB_file.write( ' comment : "SRAM";\n') - LIB_file.write( ' time_unit : "1ns";\n') - LIB_file.write( ' voltage_unit : "1V";\n') - LIB_file.write( ' current_unit : "1uA";\n') - LIB_file.write( ' leakage_power_unit : "1uW";\n') - LIB_file.write( ' nom_process : 1;\n') - LIB_file.write( ' nom_temperature : 25.000;\n') - LIB_file.write( ' nom_voltage : %s;\n' % voltage) - LIB_file.write( ' capacitive_load_unit (1,pf);\n\n') - LIB_file.write( ' pulling_resistance_unit : "1kohm";\n\n') - LIB_file.write( ' operating_conditions(tt_1.0_25.0) {\n') - LIB_file.write( ' process : 1;\n') - LIB_file.write( ' temperature : 25.000;\n') - LIB_file.write( ' voltage : %s;\n' % voltage) - LIB_file.write( ' tree_type : balanced_tree;\n') - LIB_file.write( ' }\n') - LIB_file.write( '\n') + LIB_file = open(os.sep.join([mem.results_dir, mem.fakeram_name_extension + name + '.lib']), 'w') - LIB_file.write( ' /* default attributes */\n') - LIB_file.write( ' default_cell_leakage_power : 0;\n') - LIB_file.write( ' default_fanout_load : 1;\n') - LIB_file.write( ' default_inout_pin_cap : 0.0;\n') - LIB_file.write( ' default_input_pin_cap : 0.0;\n') - LIB_file.write( ' default_output_pin_cap : 0.0;\n') - LIB_file.write( ' default_input_pin_cap : 0.0;\n') - LIB_file.write( ' default_max_transition : %.3f;\n\n' % max_slew) - LIB_file.write( ' default_operating_conditions : tt_1.0_25.0;\n') - LIB_file.write( ' default_leakage_power_density : 0.0;\n') - LIB_file.write( '\n') + write_init_lib(LIB_file, name, voltage, date, current_time, bits, area, max_slew, addr_width, addr_width_m1) - LIB_file.write( ' /* additional header data */\n') - LIB_file.write( ' slew_derate_from_library : 1.000;\n') - LIB_file.write( ' slew_lower_threshold_pct_fall : 20.000;\n') - LIB_file.write( ' slew_upper_threshold_pct_fall : 80.000;\n') - LIB_file.write( ' slew_lower_threshold_pct_rise : 20.000;\n') - LIB_file.write( ' slew_upper_threshold_pct_rise : 80.000;\n') - LIB_file.write( ' input_threshold_pct_fall : 50.000;\n') - LIB_file.write( ' input_threshold_pct_rise : 50.000;\n') - LIB_file.write( ' output_threshold_pct_fall : 50.000;\n') - LIB_file.write( ' output_threshold_pct_rise : 50.000;\n\n') - LIB_file.write( '\n') + if num_rport > 0: + write_clk_ports(LIB_file, num_rport, 'r', name, slew_indicies, min_driver_in_cap, clkpindynamic, min_period) + write_lib_ports(LIB_file, num_rport, 'r', name, max_load, slew_indicies, load_indicies, tcq, \ + min_slew, max_slew, min_driver_in_cap, tsetup, thold, pindynamic) - LIB_file.write( ' lu_table_template(%s_mem_out_delay_template) {\n' % name ) - LIB_file.write( ' variable_1 : input_net_transition;\n') - LIB_file.write( ' variable_2 : total_output_net_capacitance;\n') - LIB_file.write( ' index_1 ("1000, 1001");\n') - LIB_file.write( ' index_2 ("1000, 1001");\n') - LIB_file.write( ' }\n') - LIB_file.write( ' lu_table_template(%s_mem_out_slew_template) {\n' % name ) - LIB_file.write( ' variable_1 : total_output_net_capacitance;\n') - LIB_file.write( ' index_1 ("1000, 1001");\n') - LIB_file.write( ' }\n') - LIB_file.write( ' lu_table_template(%s_constraint_template) {\n' % name ) - LIB_file.write( ' variable_1 : related_pin_transition;\n') - LIB_file.write( ' variable_2 : constrained_pin_transition;\n') - LIB_file.write( ' index_1 ("1000, 1001");\n') - LIB_file.write( ' index_2 ("1000, 1001");\n') - LIB_file.write( ' }\n') - LIB_file.write( ' power_lut_template(%s_energy_template_clkslew) {\n' % name ) - LIB_file.write( ' variable_1 : input_transition_time;\n') - LIB_file.write( ' index_1 ("1000, 1001");\n') - LIB_file.write( ' }\n') - LIB_file.write( ' power_lut_template(%s_energy_template_sigslew) {\n' % name ) - LIB_file.write( ' variable_1 : input_transition_time;\n') - LIB_file.write( ' index_1 ("1000, 1001");\n') - LIB_file.write( ' }\n') - LIB_file.write( ' library_features(report_delay_calculation);\n') - LIB_file.write( ' type (%s_DATA) {\n' % name ) - LIB_file.write( ' base_type : array ;\n') - LIB_file.write( ' data_type : bit ;\n') - LIB_file.write( ' bit_width : %d;\n' % bits) - LIB_file.write( ' bit_from : %d;\n' % (int(bits)-1)) - LIB_file.write( ' bit_to : 0 ;\n') - LIB_file.write( ' downto : true ;\n') - LIB_file.write( ' }\n') - LIB_file.write( ' type (%s_ADDRESS) {\n' % name) - LIB_file.write( ' base_type : array ;\n') - LIB_file.write( ' data_type : bit ;\n') - LIB_file.write( ' bit_width : %d;\n' % addr_width) - LIB_file.write( ' bit_from : %d;\n' % addr_width_m1) - LIB_file.write( ' bit_to : 0 ;\n') - LIB_file.write( ' downto : true ;\n') - LIB_file.write( ' }\n') - if byte_write: - LIB_file.write( ' type (%s_WMASK) {\n' % name) - LIB_file.write( ' base_type : array ;\n') - LIB_file.write( ' data_type : bit ;\n') - LIB_file.write( ' bit_width : %d;\n' % (bits // 8)) - LIB_file.write( ' bit_from : %d;\n' % ((bits // 8)-1)) - LIB_file.write( ' bit_to : 0 ;\n') - LIB_file.write( ' downto : true ;\n') - LIB_file.write( ' }\n') + if num_wport > 0: + write_clk_ports(LIB_file, num_wport, 'w', name, slew_indicies, min_driver_in_cap, clkpindynamic, min_period) - LIB_file.write( 'cell(%s) {\n' % name ) - LIB_file.write( ' area : %.3f;\n' % area) - #LIB_file.write( ' dont_use : true;\n') - #LIB_file.write( ' dont_touch : true;\n') - LIB_file.write( ' interface_timing : true;\n') - LIB_file.write( ' memory() {\n') - LIB_file.write( ' type : ram;\n') - LIB_file.write( ' address_width : %d;\n' % addr_width) - LIB_file.write( ' word_width : %d;\n' % bits) - LIB_file.write( ' }\n') + write_lib_ports(LIB_file, num_wport, 'w', name, max_load, slew_indicies, load_indicies, tcq, \ + min_slew, max_slew, min_driver_in_cap, tsetup, thold, pindynamic) + if has_wmask: + write_lib_wmask(LIB_file, num_wport, 'w', name, slew_indicies, min_driver_in_cap, tsetup, thold, pindynamic) - for i in unique_clks: - if (len(unique_clks) == 1) : - LIB_file.write(' pin(clk) {\n') - else: - LIB_file.write(' pin(clk%s) {\n' % (i)) - LIB_file.write(' direction : input;\n') - LIB_file.write(' capacitance : %.3f;\n' % (min_driver_in_cap*5)) ;# Clk pin is usually higher cap for fanout control, assuming an x5 driver. - LIB_file.write(' clock : true;\n') - #LIB_file.write(' max_transition : 0.01;\n') # Max rise/fall time - #LIB_file.write(' min_pulse_width_high : %.3f ;\n' % (min_period)) - #LIB_file.write(' min_pulse_width_low : %.3f ;\n' % (min_period)) - LIB_file.write(' min_period : %.3f ;\n' % (min_period)) - #LIB_file.write(' minimum_period(){\n') - #LIB_file.write(' constraint : %.3f ;\n' % min_period) - #LIB_file.write(' when : "1";\n') - #LIB_file.write(' sdf_cond : "1";\n') - #LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' rise_power(%s_energy_template_clkslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (clkpindynamic, clkpindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_clkslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (clkpindynamic, clkpindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write('\n') + if num_rwport > 0: + write_clk_ports(LIB_file, num_rwport, 'rw', name, slew_indicies, min_driver_in_cap, clkpindynamic, min_period) + + write_lib_ports(LIB_file, num_rwport, 'rw', name, max_load, slew_indicies, load_indicies, tcq, \ + min_slew, max_slew, min_driver_in_cap, tsetup, thold, pindynamic) + if has_wmask: + write_lib_wmask(LIB_file, num_rwport, 'rw', name, slew_indicies, min_driver_in_cap, tsetup, thold, pindynamic) - for i in range(int(num_rwport)) : - LIB_file.write(' bus(rd_out_rw%s) {\n' % (i + 1)) - LIB_file.write(' bus_type : %s_DATA;\n' % name) - LIB_file.write(' direction : output;\n') - LIB_file.write(' max_capacitance : %.3f;\n' % max_load) ;# Based on 32x inverter being a common max (or near max) inverter - LIB_file.write(' memory_read() {\n') - LIB_file.write(' address : addr_rw;\n') - LIB_file.write(' }\n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : "clk%s" ;\n' % (rw_clks[min(num_rwport, len(rw_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : rising_edge;\n') - LIB_file.write(' timing_sense : non_unate;\n') - LIB_file.write(' cell_rise(%s_mem_out_delay_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % load_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tcq, tcq)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tcq, tcq)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' cell_fall(%s_mem_out_delay_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % load_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tcq, tcq)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tcq, tcq)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' rise_transition(%s_mem_out_slew_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % load_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (min_slew, max_slew)) - LIB_file.write(' }\n') - LIB_file.write(' fall_transition(%s_mem_out_slew_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % load_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (min_slew, max_slew)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - - for i in range(int(num_rport)) : - LIB_file.write(' bus(rd_out_r%s) {\n' % (i + 1)) - LIB_file.write(' bus_type : %s_DATA;\n' % name) - LIB_file.write(' direction : output;\n') - LIB_file.write(' max_capacitance : %.3f;\n' % max_load) ;# Based on 32x inverter being a common max (or near max) inverter - LIB_file.write(' memory_read() {\n') - LIB_file.write(' address : addr_r%s;\n' % (i + 1)) - LIB_file.write(' }\n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : "clk%s" ;\n' % (r_clks[min(num_rport, len(r_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : rising_edge;\n') - LIB_file.write(' timing_sense : non_unate;\n') - LIB_file.write(' cell_rise(%s_mem_out_delay_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % load_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tcq, tcq)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tcq, tcq)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' cell_fall(%s_mem_out_delay_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % load_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tcq, tcq)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tcq, tcq)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' rise_transition(%s_mem_out_slew_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % load_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (min_slew, max_slew)) - LIB_file.write(' }\n') - LIB_file.write(' fall_transition(%s_mem_out_slew_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % load_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (min_slew, max_slew)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - for i in range(int(num_rwport)) : - LIB_file.write(' pin(we_in_rw%s) {\n' % (i + 1)) - LIB_file.write(' direction : input;\n') - LIB_file.write(' capacitance : %.3f;\n' % (min_driver_in_cap)) - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (rw_clks[min(num_rwport, len(rw_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : setup_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' } \n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (rw_clks[min(num_rwport, len(rw_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : hold_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - - for i in range(int(num_wport)) : - LIB_file.write(' pin(we_in_w%s) {\n' % (i + 1)) - LIB_file.write(' direction : input;\n') - LIB_file.write(' capacitance : %.3f;\n' % (min_driver_in_cap)) - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n'% (w_clks[min(num_wport, len(w_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : setup_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' } \n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (w_clks[min(num_wport, len(w_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : hold_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - - for i in range(int(num_rwport)) : - LIB_file.write(' pin(ce_rw%s) {\n' % (i + 1)) - LIB_file.write(' direction : input;\n') - LIB_file.write(' capacitance : %.3f;\n' % (min_driver_in_cap)) - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (rw_clks[min(num_rwport, len(rw_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : setup_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' } \n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (rw_clks[min(num_rwport, len(rw_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : hold_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - - for i in range(int(num_rport)) : - LIB_file.write(' pin(ce_r%s) {\n' % (i + 1)) - LIB_file.write(' direction : input;\n') - LIB_file.write(' capacitance : %.3f;\n' % (min_driver_in_cap)) - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (r_clks[min(num_rport, len(r_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : setup_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' } \n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (r_clks[min(num_rport, len(r_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : hold_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - - for i in range(int(num_wport)) : - LIB_file.write(' pin(ce_w%s) {\n' % (i + 1)) - LIB_file.write(' direction : input;\n') - LIB_file.write(' capacitance : %.3f;\n' % (min_driver_in_cap)) - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (w_clks[min(num_wport, len(w_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : setup_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' } \n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (w_clks[min(num_wport, len(w_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : hold_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - - for i in range(int(num_rwport)) : - LIB_file.write(' bus(addr_rw%s) {\n' % (i + 1)) - LIB_file.write(' bus_type : %s_ADDRESS;\n' % name) - LIB_file.write(' direction : input;\n') - LIB_file.write(' capacitance : %.3f;\n' % (min_driver_in_cap)) - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (rw_clks[min(num_rwport, len(rw_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : setup_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' } \n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (rw_clks[min(num_rwport, len(rw_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : hold_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - - for i in range(int(num_rwport)) : - LIB_file.write(' bus(wd_in_rw%s) {\n' % (i + 1)) - LIB_file.write(' bus_type : %s_DATA;\n' % name) - LIB_file.write(' memory_write() {\n') - LIB_file.write(' address : addr_rw%s;\n' % (i + 1)) - LIB_file.write(' clocked_on : "clk%s";\n' % (rw_clks[min(num_rwport, len(rw_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' }\n') - LIB_file.write(' direction : input;\n') - LIB_file.write(' capacitance : %.3f;\n' % (min_driver_in_cap)) - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (rw_clks[min(num_rwport, len(rw_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : setup_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' } \n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (rw_clks[min(num_rwport, len(rw_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : hold_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' when : "(! (we_in_rw%s) )";\n' % (i + 1)) - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' when : "(we_in_rw%s)";\n' % (i + 1)) - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - - for i in range(int(num_wport)) : - LIB_file.write(' bus(wd_in_w%s) {\n' % (i + 1)) - LIB_file.write(' bus_type : %s_DATA;\n' % name) - LIB_file.write(' memory_write() {\n') - LIB_file.write(' address : addr_w%s;\n' % (i + 1)) - LIB_file.write(' clocked_on : "clk";\n') - LIB_file.write(' }\n') - LIB_file.write(' direction : input;\n') - LIB_file.write(' capacitance : %.3f;\n' % (min_driver_in_cap)) - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (w_clks[min(num_wport, len(w_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : setup_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' } \n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (w_clks[min(num_wport, len(w_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : hold_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' when : "(! (we_in_w%s) )";\n' % (i + 1)) - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' when : "(we_in_w%s)";\n' % (i + 1)) - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - - for i in range(int(num_rwport)) : - LIB_file.write(' bus(w_mask_rw%s) {\n' % (i + 1)) - LIB_file.write(' bus_type : %s_DATA;\n' % name) - LIB_file.write(' memory_write() {\n') - LIB_file.write(' address : addr_rw%s;\n' % (i + 1)) - LIB_file.write(' clocked_on : "clk";\n') - LIB_file.write(' }\n') - LIB_file.write(' direction : input;\n') - LIB_file.write(' capacitance : %.3f;\n' % (min_driver_in_cap)) - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (rw_clks[min(num_rwport, len(rw_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : setup_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' } \n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (rw_clks[min(num_rwport, len(rw_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : hold_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' when : "(! (we_in_rw%s) )";\n' % (i + 1)) - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' when : "(we_in_rw%s)";\n' % (i + 1)) - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - - for i in range(int(num_rport)) : - LIB_file.write(' bus(addr_r%s) {\n' % (i + 1)) - LIB_file.write(' bus_type : %s_ADDRESS;\n' % name) - LIB_file.write(' direction : input;\n') - LIB_file.write(' capacitance : %.3f;\n' % (min_driver_in_cap)) - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (r_clks[min(num_rport, len(r_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : setup_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' } \n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (r_clks[min(num_rport, len(r_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : hold_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - - for i in range(int(num_wport)) : - LIB_file.write(' bus(addr_w%s) {\n' % (i + 1)) - LIB_file.write(' bus_type : %s_ADDRESS;\n' % name) - LIB_file.write(' direction : input;\n') - LIB_file.write(' capacitance : %.3f;\n' % (min_driver_in_cap)) - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (w_clks[min(num_wport, len(w_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : setup_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' } \n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (w_clks[min(num_wport, len(w_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : hold_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - - for i in range(int(num_wport)) : - LIB_file.write(' bus(w_mask_w%s) {\n' % (i + 1)) - LIB_file.write(' bus_type : %s_DATA;\n' % name) - LIB_file.write(' memory_write() {\n') - LIB_file.write(' address : addr_w%s;\n' % (i + 1)) - LIB_file.write(' clocked_on : "clk";\n') - LIB_file.write(' }\n') - LIB_file.write(' direction : input;\n') - LIB_file.write(' capacitance : %.3f;\n' % (min_driver_in_cap)) - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (w_clks[min(num_wport, len(w_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : setup_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (tsetup, tsetup)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (tsetup, tsetup)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' } \n') - LIB_file.write(' timing() {\n') - LIB_file.write(' related_pin : clk%s;\n' % (w_clks[min(num_wport, len(w_clks) - 1)] if len(unique_clks) != 1 else '')) - LIB_file.write(' timing_type : hold_rising ;\n') - LIB_file.write(' rise_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' fall_constraint(%s_constraint_template) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' index_2 ("%s");\n' % slew_indicies) - LIB_file.write(' values ( \\\n') - LIB_file.write(' "%.3f, %.3f", \\\n' % (thold, thold)) - LIB_file.write(' "%.3f, %.3f" \\\n' % (thold, thold)) - LIB_file.write(' )\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' when : "(! (we_in_w%s) )";\n' % (i + 1)) - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' internal_power(){\n') - LIB_file.write(' when : "(we_in_w%s)";\n' % (i + 1)) - LIB_file.write(' rise_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' fall_power(%s_energy_template_sigslew) {\n' % name) - LIB_file.write(' index_1 ("%s");\n' % slew_indicies) - LIB_file.write(' values ("%.3f, %.3f")\n' % (pindynamic, pindynamic)) - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' }\n') - LIB_file.write(' cell_leakage_power : %.3f;\n' % (leakage)) LIB_file.write('}\n') @@ -1042,3 +103,496 @@ def generate_lib( mem ): LIB_file.close() +######################################## +# Helper functions +######################################## + +def write_init_lib(LIB_file, name, voltage, date, current_time, bits, area, max_slew, addr_width, addr_width_m1) -> None: + + LIB_file.write(f'library({name}) {{\n' + f' technology (cmos);\n' + f' delay_model : table_lookup;\n' + f' revision : 1.0;\n' + f' date : "{date} {current_time}";\n' + f' comment : "SRAM";\n' + f' time_unit : "1ns";\n' + f' voltage_unit : "1V";\n' + f' current_unit : "1uA";\n' + f' leakage_power_unit : "1uW";\n' + f' nom_process : 1;\n' + f' nom_temperature : 25.000;\n' + f' nom_voltage : {voltage};\n' + f' capacitive_load_unit (1,pf);\n\n' + f' pulling_resistance_unit : "1kohm";\n\n' + f' operating_conditions(tt_1.0_25.0) {{\n' + f' process : 1;\n' + f' temperature : 25.000;\n' + f' voltage : {voltage};\n' + f' tree_type : balanced_tree;\n' + f' }}\n' + f'\n') + + LIB_file.write( f' /* default attributes */\n' + f' default_cell_leakage_power : 0;\n' + f' default_fanout_load : 1;\n' + f' default_inout_pin_cap : 0.0;\n' + f' default_input_pin_cap : 0.0;\n' + f' default_output_pin_cap : 0.0;\n' + f' default_input_pin_cap : 0.0;\n' + f' default_max_transition : {max_slew:.3f};\n\n' + f' default_operating_conditions : tt_1.0_25.0;\n' + f' default_leakage_power_density : 0.0;\n' + f'\n') + + LIB_file.write( f' /* additional header data */\n' + f' slew_derate_from_library : 1.000;\n' + f' slew_lower_threshold_pct_fall : 20.000;\n' + f' slew_upper_threshold_pct_fall : 80.000;\n' + f' slew_lower_threshold_pct_rise : 20.000;\n' + f' slew_upper_threshold_pct_rise : 80.000;\n' + f' input_threshold_pct_fall : 50.000;\n' + f' input_threshold_pct_rise : 50.000;\n' + f' output_threshold_pct_fall : 50.000;\n' + f' output_threshold_pct_rise : 50.000;\n\n' + f'\n' + f' lu_table_template({name}_mem_out_delay_template) {{\n' + f' variable_1 : input_net_transition;\n' + f' variable_2 : total_output_net_capacitance;\n' + f' index_1 ("1000, 1001");\n' + f' index_2 ("1000, 1001");\n' + f' }}\n' + f' lu_table_template({name}_mem_out_slew_template) {{\n' + f' variable_1 : total_output_net_capacitance;\n' + f' index_1 ("1000, 1001");\n' + f' }}\n' + f' lu_table_template({name}_constraint_template) {{\n' + f' variable_1 : related_pin_transition;\n' + f' variable_2 : constrained_pin_transition;\n' + f' index_1 ("1000, 1001");\n' + f' index_2 ("1000, 1001");\n' + f' }}\n' + f' power_lut_template({name}_energy_template_clkslew) {{\n' + f' variable_1 : input_transition_time;\n' + f' index_1 ("1000, 1001");\n' + f' }}\n' + f' power_lut_template({name}_energy_template_sigslew) {{\n' + f' variable_1 : input_transition_time;\n' + f' index_1 ("1000, 1001");\n' + f' }}\n' + f' library_features(report_delay_calculation);\n' + f' type ({name}_DATA) {{\n' + f' base_type : array ;\n' + f' data_type : bit ;\n' + f' bit_width : {bits};\n' + f' bit_from : {int(bits)-1};\n' + f' bit_to : 0 ;\n' + f' downto : true ;\n' + f' }}\n' + f' type ({name}_ADDRESS) {{\n' + f' base_type : array ;\n' + f' data_type : bit ;\n' + f' bit_width : {addr_width};\n' + f' bit_from : {addr_width_m1};\n' + f' bit_to : 0 ;\n' + f' downto : true ;\n' + f' }}\n') + + LIB_file.write( f' cell({name}) {{\n' + f' area : {area:.3f};\n' + #f' dont_use : true;\n' + #f' dont_touch : true;\n' + f' interface_timing : true;\n' + f' memory() {{\n' + f' type : ram;\n' + f' address_width : {addr_width};\n' + f' word_width : {bits};\n' + f' }}\n') + + +def write_lib_ports(LIB_file, num_port, type_port, name, max_load, slew_indicies, load_indicies, tcq, min_slew, max_slew, min_driver_in_cap, tsetup, thold, pindynamic) -> None: + if type_port in ('r', 'rw'): + for i in range(num_port) : + LIB_file.write(f' bus({type_port}{i}_rd_out) {{\n' + f' bus_type : {name}_DATA;\n' + f' direction : output;\n' + f' max_capacitance : {max_load:.3f};\n' # Based on 32x inverter being a common max (or near max) inverter + f' memory_read() {{\n' + f' address : {type_port}{i}_addr_in;\n' + f' }}\n' + f' timing() {{\n' + f' related_pin : "{type_port}{i}_clk" ;\n' + f' timing_type : rising_edge;\n' + f' timing_sense : non_unate;\n' + f' cell_rise({name}_mem_out_delay_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{load_indicies}");\n' + f' values ( \\\n' + f' "{tcq:.3f}, {tcq:.3f}", \\\n' + f' "{tcq:.3f}, {tcq:.3f}" \\\n' + f' )\n' + f' }}\n' + f' cell_fall({name}_mem_out_delay_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{load_indicies}");\n' + f' values ( \\\n' + f' "{tcq:.3f}, {tcq:.3f}", \\\n' + f' "{tcq:.3f}, {tcq:.3f}" \\\n' + f' )\n' + f' }}\n' + f' rise_transition({name}_mem_out_slew_template) {{\n' + f' index_1 ("{load_indicies}");\n' + f' values ("{min_slew:.3f}, {max_slew:.3f}")\n' + f' }}\n' + f' fall_transition({name}_mem_out_slew_template) {{\n' + f' index_1 ("{load_indicies}");\n' + f' values ("{min_slew:.3f}, {max_slew:.3f}")\n' + f' }}\n' + f' }}\n' + f' }}\n') + + if type_port in ('w','rw'): + for i in range(num_port) : + LIB_file.write(f' pin({type_port}{i}_we_in){{\n' + f' direction : input;\n' + f' capacitance : {min_driver_in_cap:.3f};\n' + f' timing() {{\n' + f' related_pin : "{type_port}{i}_clk";\n' + f' timing_type : setup_rising ;\n' + f' rise_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}", \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}" \\\n' + f' )\n' + f' }}\n' + f' fall_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}", \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}" \\\n' + f' )\n' + f' }}\n' + f' }} \n' + f' timing() {{\n' + f' related_pin : "{type_port}{i}_clk";\n' + f' timing_type : hold_rising ;\n' + f' rise_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{thold:.3f}, {thold:.3f}", \\\n' + f' "{thold:.3f}, {thold:.3f}" \\\n' + f' )\n' + f' }}\n' + f' fall_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{thold:.3f}, {thold:.3f}", \\\n' + f' "{thold:.3f}, {thold:.3f}" \\\n' + f' )\n' + f' }}\n' + f' }}\n' + f' internal_power(){{\n' + f' rise_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' fall_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' }}\n' + f' }}\n') + + for i in range(num_port) : + LIB_file.write(f' bus({type_port}{i}_wd_in) {{\n' + f' bus_type : {name}_DATA;\n' + f' memory_write() {{\n' + f' address : {type_port}{i}_addr_in;\n' + f' clocked_on : "{type_port}{i}_clk";\n' + f' }}\n' + f' direction : input;\n' + f' capacitance : {min_driver_in_cap:.3f};\n' + f' timing() {{\n' + f' related_pin : "{type_port}{i}_clk";\n' + f' timing_type : setup_rising ;\n' + f' rise_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}", \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}" \\\n' + f' )\n' + f' }}\n' + f' fall_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}", \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}" \\\n' + f' )\n' + f' }}\n' + f' }} \n' + f' timing() {{\n' + f' related_pin : "{type_port}{i}_clk";\n' + f' timing_type : hold_rising ;\n' + f' rise_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{thold:.3f}, {thold:.3f}", \\\n' + f' "{thold:.3f}, {thold:.3f}" \\\n' + f' )\n' + f' }}\n' + f' fall_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{thold:.3f}, {thold:.3f}", \\\n' + f' "{thold:.3f}, {thold:.3f}" \\\n' + f' )\n' + f' }}\n' + f' }}\n' + f' internal_power(){{\n' + f' when : "(! ({type_port}{i}_we_in) )";\n' + f' rise_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' fall_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' }}\n' + f' internal_power(){{\n' + f' when : "({type_port}{i}_we_in)";\n' + f' rise_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' fall_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' }}\n' + f' }}\n') + + + for i in range(num_port): + LIB_file.write(f' pin({type_port}{i}_ce_in){{\n' + f' direction : input;\n' + f' capacitance : {min_driver_in_cap:.3f};\n' + f' timing() {{\n' + f' related_pin : "{type_port}{i}_clk";\n' + f' timing_type : setup_rising ;\n' + f' rise_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}", \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}" \\\n' + f' )\n' + f' }}\n' + f' fall_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}", \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}" \\\n' + f' )\n' + f' }}\n' + f' }} \n' + f' timing() {{\n' + f' related_pin : "{type_port}{i}_clk";\n' + f' timing_type : hold_rising ;\n' + f' rise_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{thold:.3f}, {thold:.3f}", \\\n' + f' "{thold:.3f}, {thold:.3f}" \\\n' + f' )\n' + f' }}\n' + f' fall_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{thold:.3f}, {thold:.3f}", \\\n' + f' "{thold:.3f}, {thold:.3f}" \\\n' + f' )\n' + f' }}\n' + f' }}\n' + f' internal_power(){{\n' + f' rise_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' fall_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' }}\n' + f' }}\n') + + for i in range(num_port) : + LIB_file.write(f' bus({type_port}{i}_addr_in) {{\n' + f' bus_type : {name}_ADDRESS;\n' + f' direction : input;\n' + f' capacitance : {min_driver_in_cap:.3f};\n' + f' timing() {{\n' + f' related_pin : "{type_port}{i}_clk";\n' + f' timing_type : setup_rising ;\n' + f' rise_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}", \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}" \\\n' + f' )\n' + f' }}\n' + f' fall_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}", \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}" \\\n' + f' )\n' + f' }}\n' + f' }} \n' + f' timing() {{\n' + f' related_pin : "{type_port}{i}_clk";\n' + f' timing_type : hold_rising ;\n' + f' rise_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{thold:.3f}, {thold:.3f}", \\\n' + f' "{thold:.3f}, {thold:.3f}" \\\n' + f' )\n' + f' }}\n' + f' fall_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{thold:.3f}, {thold:.3f}", \\\n' + f' "{thold:.3f}, {thold:.3f}" \\\n' + f' )\n' + f' }}\n' + f' }}\n' + f' internal_power(){{\n' + f' rise_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' fall_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' }}\n' + f' }}\n') + +def write_clk_ports(LIB_file, num_port, type_port, name, slew_indicies, min_driver_in_cap, clkpindynamic, min_period) -> None: + for i in range(num_port): + LIB_file.write( f' pin({type_port}{i}_clk) {{\n' + f' direction : input;\n' + f' capacitance : {min_driver_in_cap*5:.3f};\n' # Clk pin is usually higher cap for fanout control, assuming an x5 driver. + f' clock : true;\n' + #f' max_transition : 0.01;\n') # Max rise/fall time + #f' min_pulse_width_high : {min_period:.3f} ;\n' + #f' min_pulse_width_low : {min_period:.3f} ;\n' + f' min_period : {min_period:.3f} ;\n' + #f' minimum_period(){\n' + #f' constraint : %.3f ;\n' % min_period + #f' when : "1";\n' + #f' sdf_cond : "1";\n' + #f' }\n' + f' internal_power(){{\n' + f' rise_power({name}_energy_template_clkslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{clkpindynamic:.3f}, {clkpindynamic:.3f}")\n' + f' }}\n' + f' fall_power({name}_energy_template_clkslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{clkpindynamic:.3f}, {clkpindynamic:.3f}")\n' + f' }}\n' + f' }}\n' + f' }}\n' + f'\n') + +def write_lib_wmask(LIB_file, num_port, type_port, name, slew_indicies, min_driver_in_cap, tsetup, thold, pindynamic) -> None: + for i in range(int(num_port)) : + LIB_file.write(f' bus({type_port}{i}_wmask_in) {{\n' + f' bus_type : {name}_DATA;\n' + f' memory_write() {{\n' + f' address : {type_port}{i}_addr_in;\n' + f' clocked_on : "{type_port}{i}_clk";\n' + f' }}\n' + f' direction : input;\n' + f' capacitance : {min_driver_in_cap:.3f};\n' + f' timing() {{\n' + f' related_pin : {type_port}{i}_clk;\n' + f' timing_type : setup_rising ;\n' + f' rise_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}", \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}" \\\n' + f' )\n' + f' }}\n' + f' fall_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}", \\\n' + f' "{tsetup:.3f}, {tsetup:.3f}" \\\n' + f' )\n' + f' }}\n' + f' }} \n' + f' timing() {{\n' + f' related_pin : {type_port}{i}_clk;\n' + f' timing_type : hold_rising ;\n' + f' rise_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{thold:.3f}, {thold:.3f}", \\\n' + f' "{thold:.3f}, {thold:.3f}" \\\n' + f' )\n' + f' }}\n' + f' fall_constraint({name}_constraint_template) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' index_2 ("{slew_indicies}");\n' + f' values ( \\\n' + f' "{thold:.3f}, {thold:.3f}", \\\n' + f' "{thold:.3f}, {thold:.3f}" \\\n' + f' )\n' + f' }}\n' + f' }}\n' + f' internal_power(){{\n' + f' when : "(! ({type_port}{i}_we_in) )";\n' + f' rise_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' fall_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' }}\n' + f' internal_power(){{\n' + f' when : "({type_port}{i}_we_in)";\n' + f' rise_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' fall_power({name}_energy_template_sigslew) {{\n' + f' index_1 ("{slew_indicies}");\n' + f' values ("{pindynamic:.3f}, {pindynamic:.3f}")\n' + f' }}\n' + f' }}\n' + f' }}\n') + diff --git a/scripts/utils/generate_verilog.py b/scripts/utils/generate_verilog.py index 14dcca0..7bdf046 100644 --- a/scripts/utils/generate_verilog.py +++ b/scripts/utils/generate_verilog.py @@ -2,605 +2,246 @@ import math ################################################################################ -# Generate a .v file based on the given SRAM matching the exact interface +# GENERATE VERILOG VIEW +# +# Generate a .v file based on the given SRAM. ################################################################################ -def generate_verilog(mem, tmChkExpand=False): - '''Generate a verilog view for the RAM''' - name = str(mem.name) - depth = int(mem.depth) - bits = int(mem.width_in_bits) - addr_width = math.ceil(math.log2(depth)) - crpt_on_x = 1 - - # New Additional vars: - write_mode = str(mem.write_mode) - rw_clks, r_clks, w_clks = mem.port_clks +def generate_verilog( mem ): + + name = str(mem.name) + depth = int(mem.depth) + bits = int(mem.width_in_bits) + addr_width = math.ceil(math.log2(depth)) + num_rport = mem.r + num_wport = mem.w + num_rwport = mem.rw + num_wmask = mem.wmask + write_mode = mem.write_mode + has_wmask = mem.has_write_mask + write_granularity = mem.write_granularity + + + V_file = open(os.sep.join([mem.results_dir, mem.fakeram_name_extension + name + '.v']), 'w') + + V_file.write('module %s\n' % name) + V_file.write('(\n') + + ######################################## + # Init IO + ######################################## + if num_rport > 0: + write_init_port_names( V_file, num_rport, num_wmask, has_wmask, 'r') + if num_wport > 0: + write_init_port_names( V_file, num_wport, num_wmask, has_wmask, 'w') + if num_rwport > 0: + write_init_port_names( V_file, num_rwport, num_wmask, has_wmask, 'rw') - unique_clks = list(set(x for sub in mem.port_clks for x in sub)) - - byte_write = 1 if mem.write_granularity == 8 else 0 - # Change masking based on wether byte write attribute exists or not + V_file.write(');\n') + V_file.write(' parameter BITS = %s;\n' % str(bits)) + V_file.write(' parameter WORD_DEPTH = %s;\n' % str(depth)) + V_file.write(' parameter ADDR_WIDTH = %s;\n' % str(addr_width)) + V_file.write(' parameter corrupt_mem_on_X_p = 1;\n') + V_file.write('\n') + - ############################################# - ### Generate 'setuphold' timing checks ### - ############################################# - - # Shortened per-bit and per-signal checks - setuphold_checks='' - # rw ports setuphold check - clk_ctr = 0 - for ct in range(mem.rw_ports): - # Initial check to see if future r or w ports exist (such that we'll label clk differently) - if len(unique_clks) == 1: clk_suff = '' - elif clk_ctr < len(rw_clks): - clk_suff=rw_clks[clk_ctr] - clk_ctr+=1 - setuphold_checks += f' $setuphold (posedge clk{clk_suff}, we_in_rw{ct+1}, 0, 0, notifier);\n' - setuphold_checks += f' $setuphold (posedge clk{clk_suff}, ce_rw{ct+1}, 0, 0, notifier);\n' - if tmChkExpand: # per-bit - for i in range(addr_width): setuphold_checks += f' $setuphold (posedge clk{clk_suff}, addr_rw{ct+1}{i}, 0, 0, notifier);\n' - for i in range( bits): setuphold_checks += f' $setuphold (posedge clk{clk_suff}, wd_in{ct+1}{i}, 0, 0, notifier);\n' - for i in range( bits): setuphold_checks += f' $setuphold (posedge clk{clk_suff}, w_mask_rw{ct+1}{i}, 0, 0, notifier);\n' - else: # per-sig - setuphold_checks += f' $setuphold (posedge clk{clk_suff}, addr_rw{ct+1}, 0, 0, notifier);\n' - setuphold_checks += f' $setuphold (posedge clk{clk_suff}, wd_in{ct+1}, 0, 0, notifier);\n' - setuphold_checks += f' $setuphold (posedge clk{clk_suff}, w_mask_rw{ct+1}, 0, 0, notifier);\n' - # r ports setuphold check - clk_ctr = 0 - for ct in range(mem.r_ports): - if len(unique_clks) == 1: clk_suff = '' - elif clk_ctr < len(r_clks): - clk_suff=r_clks[clk_ctr] - clk_ctr+=1 - setuphold_checks += f' $setuphold (posedge clk{clk_suff}, ce_r{ct+1}, 0, 0, notifier);\n' - if tmChkExpand: # per-bit - for i in range(addr_width): setuphold_checks += f' $setuphold (posedge clk{clk_suff}, addr_r{ct+1}{i}, 0, 0, notifier);\n' - else: # per-sig - setuphold_checks += f' $setuphold (posedge clk{clk_suff}, addr_r{ct+1}, 0, 0, notifier);\n' + if num_rport > 0: + write_ports( V_file, num_rport, num_wmask, has_wmask,'r') + if num_wport > 0: + write_ports( V_file, num_wport, num_wmask, has_wmask,'w') + if num_rwport > 0: + write_ports( V_file, num_rwport, num_wmask, has_wmask,'rw') + V_file.write(f'\n' + f' reg [BITS-1:0] mem [0:WORD_DEPTH-1];\n' + f' integer j;\n\n') + + ######################################## + # Port Logic + ######################################## + if num_rport > 0: + write_logic( V_file, name, num_rport, write_mode, num_wmask, has_wmask, write_granularity, 'r' ) + if num_wport > 0: + write_logic( V_file, name, num_wport, write_mode, num_wmask, has_wmask, write_granularity, 'w' ) + if num_rwport > 0: + write_logic( V_file, name, num_rwport, write_mode, num_wmask, has_wmask, write_granularity, 'rw' ) + V_file.write('\n') + + + ######################################## + # Timing Checks + ######################################## + V_file.write(f' // Timing check placeholders (will be replaced during SDF back-annotation)\n' + f' `ifdef SRAM_TIMING' + f' reg notifier;\n' + f' specify\n') + + if num_rport > 0: + write_timing_checks( V_file, num_rport, has_wmask, 'r') + if num_wport > 0: + write_timing_checks( V_file, num_wport, has_wmask, 'w') + if num_rwport > 0: + write_timing_checks( V_file, num_rwport, has_wmask, 'rw') + V_file.write(f' endspecify\n' + f' `endif\n' + f'\n' + f'endmodule\n') + + V_file.close() + +################################################################################ +# GENERATE VERILOG BLACKBOX VIEW +# +# Generate a .bb.v file based on the given SRAM. This is the same as the +# standard verilog view but only has the module definition and port +# declarations (no internal logic). +################################################################################ + +def generate_verilog_bb( mem ): + + name = str(mem.name) + depth = int(mem.depth) + bits = int(mem.width_in_bits) + num_rport = mem.r + num_wport = mem.w + num_rwport = mem.rw + write_mode = mem.write_mode + addr_width = math.ceil(math.log2(depth)) + num_wmask = mem.wmask + has_wmask = mem.has_write_mask + + + V_file = open(os.sep.join([mem.results_dir, mem.fakeram_name_extension + name + '.bb.v']), 'w') + + V_file.write('module %s\n' % name) + V_file.write('(\n') + + ######################################## + # Init IO + ######################################## + if num_rport > 0: + write_init_port_names( V_file, num_rport, num_wmask, has_wmask, 'r' ) + if num_wport > 0: + write_init_port_names( V_file, num_wport, num_wmask, has_wmask, 'w' ) + if num_rwport > 0: + write_init_port_names( V_file, num_rwport, num_wmask, has_wmask,'rw' ) + + V_file.write(');\n') + V_file.write(' parameter BITS = %s;\n' % str(bits)) + V_file.write(' parameter WORD_DEPTH = %s;\n' % str(depth)) + V_file.write(' parameter ADDR_WIDTH = %s;\n' % str(addr_width)) + V_file.write(' parameter corrupt_mem_on_X_p = 1;\n') + V_file.write('\n') + + if num_rport > 0: + write_ports( V_file, num_rport, num_wmask, has_wmask, 'r') + if num_wport > 0: + write_ports( V_file, num_wport, num_wmask, has_wmask, 'w') + if num_rwport > 0: + write_ports( V_file, num_rwport, num_wmask, has_wmask, 'rw') + + V_file.write('\n') + V_file.write('endmodule\n') + V_file.close() + +######################################## +# Helper functions +######################################## + +def write_init_port_names( V_file, num_port, num_wmask, has_wmask, type_port) -> None: + for i in range(int(num_port)): + if type_port in ('w', 'rw'): + V_file.write(f' {type_port}{i}_wd_in,\n' + f' {type_port}{i}_we_in,\n') + if has_wmask: + V_file.write(f' {type_port}{i}_wmask_in,\n') + + if type_port in ('r', 'rw'): + V_file.write(f' {type_port}{i}_rd_out,\n') + + V_file.write(f' {type_port}{i}_clk,\n' + f' {type_port}{i}_ce_in,\n' + f' {type_port}{i}_addr_in,\n') + + +def write_ports( V_file, num_port, num_wmask, has_wmask, type_port) -> None: + for i in range(num_port): + V_file.write(f' input {type_port}{i}_clk;\n' + f' input {type_port}{i}_ce_in;\n' + f' input [ADDR_WIDTH-1:0] {type_port}{i}_addr_in;\n') + + if type_port in ('r', 'rw'): + V_file.write(f' output reg [BITS-1:0] {type_port}{i}_rd_out;\n') + + if type_port in ('w', 'rw'): + V_file.write(f' input {type_port}{i}_we_in;\n' + f' input [BITS-1:0] {type_port}{i}_wd_in;\n') + if has_wmask: + V_file.write(f' input [{num_wmask-1}:0] {type_port}{i}_wmask_in;\n') + +def write_logic( V_file, name, num_port, write_mode, num_wmask, has_wmask, write_granularity, type_port): + for i in range(int(num_port)): + V_file.write(f' always @(posedge {type_port}{i}_clk) begin\n' + f' if ({type_port}{i}_ce_in) begin\n') + # Read Logic (read_first) + if type_port in ('r', 'rw') and write_mode == 'read_first': + V_file.write(f' // Read-first\n' + f' {type_port}{i}_rd_out <= mem[{type_port}{i}_addr_in];\n') + # Write Logic + if type_port in ('w', 'rw'): + V_file.write( + # f" //if (({type_port}{i}_we_in !== 1'b1 && {type_port}{i}_we_in !== 1'b0) && corrupt_mem_on_X_p)\n" + f" // Write Port\n" + f" if (corrupt_mem_on_X_p &&\n" + f" ((^{type_port}{i}_we_in === 1'bx) || (^{type_port}{i}_addr_in === 1'bx))) begin\n" + f" // WEN or ADDR is unknown, so corrupt entire array (using unsynthesizeable for loop)\n" + f" for (j = 0; j < WORD_DEPTH; j = j + 1) begin\n" + f" mem[j] <= 'x;\n" + f' end\n' + f' $display("warning: {type_port}{i}_ce_in=1, {type_port}{i}_we_in is %b, ' + f'{type_port}{i}_addr_in = %x in {name}", {type_port}{i}_we_in, {type_port}{i}_addr_in);\n' + f" end\n" + f" else if ({type_port}{i}_we_in) begin \n") + if has_wmask: + for j in range(num_wmask): + V_file.write(f" if ({type_port}{i}_wmask_in[{j}])\n") + V_file.write(f" mem[{type_port}{i}_addr_in][{(j+1)*write_granularity-1}:{j*write_granularity}] <= ({type_port}{i}_wd_in[{(j+1)*write_granularity-1}:{j*write_granularity}]);\n") + else: + V_file.write(f" mem[{type_port}{i}_addr_in] <= {type_port}{i}_wd_in;\n") + + V_file.write(f" end\n") + + # Read Logic (write_first) + if type_port in ('r', 'rw') and write_mode == 'write_first': + V_file.write(f' // Read Port\n' + f' {type_port}{i}_rd_out <= mem[{type_port}{i}_addr_in];\n') + V_file.write(f' end\n') + # Read Corruption Logic + if type_port in ('r', 'rw'): + V_file.write(f' else begin\n' + f' // Make sure read fails if {type_port}{i}_ce_in is low\n' + f" {type_port}{i}_rd_out <= 'x;\n" + f' end\n') + V_file.write(f' end\n') + V_file.write(f'\n') + +def write_timing_checks( V_file, num_port, has_wmask, type_port) -> None: + for i in range(num_port): + V_file.write(f' (posedge {type_port}{i}_clk *> {type_port}{i}_rd_out) = (0, 0);\n') + # V_file.write('\n') + # V_file.write(' // Timing checks\n') + + for i in range(num_port): + V_file.write(f' $width (posedge {type_port}{i}_clk, 0, 0, notifier);\n' + f' $width (negedge {type_port}{i}_clk, 0, 0, notifier);\n' + f' $period (posedge {type_port}{i}_clk, 0, notifier);\n') - # w ports setuphold check - clk_ctr = 0 - for ct in range(mem.w_ports): - if len(unique_clks) == 1: clk_suff = '' - elif clk_ctr < len(w_clks): - clk_suff=w_clks[clk_ctr] - clk_ctr+=1 - setuphold_checks += f' $setuphold (posedge clk{clk_suff}, ce_w{ct+1}, 0, 0, notifier);\n' - if tmChkExpand: # per-bit - for i in range(addr_width): setuphold_checks += f' $setuphold (posedge clk{clk_suff}, addr_w{ct+1}{i}, 0, 0, notifier);\n' - for i in range( bits): setuphold_checks += f' $setuphold (posedge clk{clk_suff}, wd_in_w{ct+1}{i}, 0, 0, notifier);\n' - for i in range( bits): setuphold_checks += f' $setuphold (posedge clk{clk_suff}, w_mask_w{ct+1}{i}, 0, 0, notifier);\n' - else: # per-sig - setuphold_checks += f' $setuphold (posedge clk{clk_suff}, addr_w{ct+1}, 0, 0, notifier);\n' - setuphold_checks += f' $setuphold (posedge clk{clk_suff}, wd_in_w{ct+1}, 0, 0, notifier);\n' - setuphold_checks += f' $setuphold (posedge clk{clk_suff}, w_mask_w{ct+1}, 0, 0, notifier);\n' - ################################################# - ### END Generate 'setuphold' timing checks ### - ################################################# - fout = os.sep.join([mem.results_dir, name + '.v']) - - with open(fout, 'w') as f: - TEMPLATE_MAPPING = { - "1r1w" : VLOG_TEMPLATE_1r1w, - "2r1w" : VLOG_TEMPLATE_2r1w, - "1rw1r" : VLOG_TEMPLATE_1rw1r, - "1rw" : VLOG_TEMPLATE_1rw - } - MEM_CONFIG = { - "name": name, - "data_width" : bits, - "depth" : depth, - "addr_width" : addr_width, - "crpt_on_x" : crpt_on_x, - "setuphold_checks": setuphold_checks - } - # Memory Specific configs: - if mem.port_config == "1r1w": - MEM_CONFIG["start_of_rw_p1"] = generate_start_mode_priority(write_mode, "rd_out_r1", "addr_r1", "ce_r1") - MEM_CONFIG["end_of_rw_p1"] = generate_end_mode_priority(write_mode, "rd_out_r1", "addr_r1", "ce_r1") - MEM_CONFIG["byte_write_logic"] = generate_byte_write_logic(byte_write, bits // 8, 'w1') - elif mem.port_config == "2r1w": - MEM_CONFIG["start_of_rw_p1"] = generate_start_mode_priority(write_mode, "rd_out_r1", "addr_r1", "ce_r1") - MEM_CONFIG["end_of_rw_p1"] = generate_end_mode_priority(write_mode, "rd_out_r1", "addr_r1", "ce_r1") - MEM_CONFIG["start_of_rw_p2"] = generate_start_mode_priority(write_mode, "rd_out_r2", "addr_r2", "ce_r2") - MEM_CONFIG["end_of_rw_p2"] = generate_end_mode_priority(write_mode, "rd_out_r2", "addr_r2", "ce_r2") - MEM_CONFIG["byte_write_logic"] = generate_byte_write_logic(byte_write, bits // 8, 'w1') - elif mem.port_config == "1rw1r" or mem.port_config == "1rw": - MEM_CONFIG["start_of_rw_p1"] = generate_start_mode_priority(write_mode, "rd_out_rw1", "addr_rw1") - MEM_CONFIG["end_of_rw_p1"] = generate_end_mode_priority(write_mode, "rd_out_rw1", "addr_rw1") - MEM_CONFIG["byte_write_logic"] = generate_byte_write_logic(byte_write, bits // 8, 'rw1') - elif mem.port_config not in TEMPLATE_MAPPING: - print(f"Listed config '{mem.port_config}' doesn't exist!\nExiting...\n") - exit(1) - # Generate RW port logic based on write mode - f.write(TEMPLATE_MAPPING[mem.port_config].format(**MEM_CONFIG)) - - - - -def generate_verilog_bb(mem): - '''Generate a verilog black-box view for the RAM''' - name = str(mem.name) - depth = int(mem.depth) - bits = int(mem.width_in_bits) - addr_width = math.ceil(math.log2(depth)) - has_rport = hasattr(mem, 'r_ports') and mem.r_ports > 0 - byte_write = hasattr(mem, 'write_granularity') and mem.write_granularity == 8 - crpt_on_x = 1 - fout = os.sep.join([mem.results_dir, name + '.bb.v']) - with open(fout, 'w') as f: - # Prepare byte-write parameters for black box - BB_TEMPLATE_MAPPING = { - "1r1w" : VLOG_BB_TEMPLATE_1r1w, - "2r1w" : VLOG_BB_TEMPLATE_2r1w, - "1rw1r" : VLOG_BB_TEMPLATE_1rw1r, - "1rw" : VLOG_BB_TEMPLATE_1rw - } - # Configs that all fakeram memory will require - BB_MEM_CONFIG = { - "name": name, - "data_width" : bits, - "depth" : depth, - "addr_width" : addr_width, - "crpt_on_x" : crpt_on_x - } - - if mem.port_config not in BB_TEMPLATE_MAPPING: - print(f"Listed config '{mem.port_config}' doesn't exist!\n") - exit(1) - - f.write(BB_TEMPLATE_MAPPING[mem.port_config].format(**BB_MEM_CONFIG)) - -# Dynamic write mode setup. Specialized check for ce read mode -def generate_start_mode_priority(write_mode, regname, addrname, ce_r=None,): - if write_mode == 'read_first': - if (ce_r): - return f''' - if ({ce_r}) - {regname} <= mem[{addrname}]; - else - {regname} <= 'x;''' - else: - return f"{regname} <= mem[{addrname}];" - else: - return '' - -def generate_end_mode_priority(write_mode, regname, addrname, ce_r=None,): - if write_mode == 'read_first': - return '' - else: - if (ce_r): - return f''' - if ({ce_r}) - {regname} <= mem[{addrname}]; - else - {regname} <= 'x;''' - else: - return f"{regname} <= mem[{addrname}];" - -def generate_byte_write_logic(byte_write, num_bytes, portnum): - '''Generate the byte-write logic for memories that support it''' - if not byte_write: - return f'mem[addr_{portnum}] <= (wd_in_{portnum} & w_mask_{portnum}) | (mem[addr_{portnum}] & ~w_mask_{portnum});' - else: - return f'mem[addr_{portnum}][{num_bytes*8+7}:{num_bytes*8}] <= (wd_in_{portnum}[{num_bytes*8+7}:{num_bytes*8}] & w_mask_{portnum}[{num_bytes*8+7}:{num_bytes*8}]) | (mem[addr_{portnum}][{num_bytes*8+7}:{num_bytes*8}] & ~w_mask_{portnum}[{num_bytes*8+7}:{num_bytes*8}]);' - - -VLOG_TEMPLATE_1r1w = '''\ -module {name} -( - clk, - rd_out_r1, - addr_r1, - addr_w1, - we_in_w1, - wd_in_w1, - w_mask_w1, - ce_r1, - ce_w1 -); - parameter BITS = {data_width}; - parameter WORD_DEPTH = {depth}; - parameter ADDR_WIDTH = {addr_width}; - parameter corrupt_mem_on_X_p = {crpt_on_x}; - - output reg [BITS-1:0] rd_out_r1; - input [ADDR_WIDTH-1:0] addr_r1; - input [ADDR_WIDTH-1:0] addr_w1; - input we_in_w1; - input [BITS-1:0] wd_in_w1; - input [BITS-1:0] w_mask_w1; - input clk; - input ce_r1; - input ce_w1; - - reg [BITS-1:0] mem [0:WORD_DEPTH-1]; - integer j; - - always @(posedge clk) - begin - // Write port - {start_of_rw_p1} - if (ce_w1) - begin - if (corrupt_mem_on_X_p && ((^we_in_w === 1'bx) || (^addr_w === 1'bx))) - begin - for (j = 0; j < WORD_DEPTH; j = j + 1) - mem[j] <= 'x; - end - else if (we_in_w1) - begin - {byte_write_logic} - end - end - {end_of_rw_p1} - end - `ifdef SRAM_TIMING - reg notifier; - specify - (posedge clk *> rd_out_r1) = (0, 0); - $width (posedge clk, 0, 0, notifier); - $width (negedge clk, 0, 0, notifier); - $period (posedge clk, 0, notifier); -{setuphold_checks} - endspecify - `endif - -endmodule -''' - -VLOG_TEMPLATE_2r1w = '''\ -module {name} -( - clk, - rd_out_r1, - rd_out_r2, - addr_r1, - addr_r2, - addr_w1, - we_in_w1, - wd_in_w1, - w_mask_w1, - ce_r1, - ce_r2, - ce_w1 -); - parameter BITS = {data_width}; - parameter WORD_DEPTH = {depth}; - parameter ADDR_WIDTH = {addr_width}; - parameter corrupt_mem_on_X_p = {crpt_on_x}; - - output reg [BITS-1:0] rd_out_r1; - output reg [BITS-1:0] rd_out_r2; - input [ADDR_WIDTH-1:0] addr_r1; - input [ADDR_WIDTH-1:0] addr_r2; - input [ADDR_WIDTH-1:0] addr_w1; - input we_in_w1; - input [BITS-1:0] wd_in_w1; - input [BITS-1:0] w_mask_w1; - input clk; - input ce_r1; - input ce_r2; - input ce_w1; - - reg [BITS-1:0] mem [0:WORD_DEPTH-1]; - integer j; - - always @(posedge clk) - begin - {start_of_rw_p1} - {start_of_rw_p2} - // Write port - if (ce_w1) - begin - if (corrupt_mem_on_X_p && ((^we_in_w === 1'bx) || (^addr_w === 1'bx))) - begin - for (j = 0; j < WORD_DEPTH; j = j + 1) - mem[j] <= 'x; - end - else if (we_in_w1) - begin - {byte_write_logic} - end - end - {end_of_rw_p1} - {end_of_rw_p1} - end - - `ifdef SRAM_TIMING - reg notifier; - specify - (posedge clk *> rd_out_r1) = (0, 0); - (posedge clk *> rd_out_r2) = (0, 0); - $width (posedge clk, 0, 0, notifier); - $width (negedge clk, 0, 0, notifier); - $period (posedge clk, 0, notifier); -{setuphold_checks} - endspecify - `endif -endmodule ''' - -VLOG_TEMPLATE_1rw1r = '''\ -module {name} ( - // Port 0: RW (Write/Read Port) - clk0, - ce_rw1, - we_in_rw1, - w_mask_rw1, - addr_rw1, - wd_in_rw1, - rd_out_rw1, - // Port 1: R (Read-Only Port) - clk1, - ce_r1, - addr_r1, - rd_out_r1 -); - - parameter BITS = {data_width}; - parameter WORD_DEPTH = {depth}; - parameter ADDR_WIDTH = {addr_width}; - - - // Port 0: RW - input clk0; - input ce_rw1; - input we_in_rw1; - input [BITS-1:0] w_mask_rw1; - input [ADDR_WIDTH-1:0] addr_rw1; - input [BITS-1:0] wd_in_rw1; - output [BITS-1:0] rd_out_rw1; - - // Port 1: R - input clk1; - input ce_r1; - input [ADDR_WIDTH-1:0] addr_r1; - output [BITS-1:0] rd_out_r1; - - // Memory array - reg [BITS-1:0] mem [0:WORD_DEPTH-1]; - - integer i; - - always @(posedge clk0) begin - if(ce_rw1) - begin - {start_of_rw_p1} - if (we_in_rw1) - begin - {byte_write_logic} - end - {end_of_rw_p1} - end - end - - - always @(posedge clk1) begin - if (ce_r1) begin // Active low chip select - rd_out_r1 <= mem[addr_r1]; - end - end - - - `ifdef SRAM_TIMING - reg notifier; - specify - // Delays from clk to outputs (registered outputs) - (posedge clk0 *> rd_out_rw1) = (0, 0); - (posedge clk1 *> rd_out_r1) = (0, 0); - - // Timing checks - $width (posedge clk0, 0, 0, notifier); - $width (negedge clk0, 0, 0, notifier); - $period (posedge clk0, 0, notifier); - $width (posedge clk1, 0, 0, notifier); - $width (negedge clk1, 0, 0, notifier); - $period (posedge clk1, 0, notifier); -{setuphold_checks} - endspecify - `endif - -endmodule -''' - -VLOG_TEMPLATE_1rw = '''\ -module {name} -( - rd_out_rw1, - addr_rw1, - we_in_rw1, - wd_in_rw1, - w_mask_rw1, - clk, - ce_rw1 -); - parameter BITS = {data_width}; - parameter WORD_DEPTH = {depth}; - parameter ADDR_WIDTH = {addr_width}; - parameter corrupt_mem_on_X_p = {crpt_on_x}; - - output reg [BITS-1:0] rd_out_rw1; - input [ADDR_WIDTH-1:0] addr_rw1; - input we_in_rw1; - input [BITS-1:0] wd_in_rw1; - input [BITS-1:0] w_mask_rw1; - input clk; - input ce_rw1; - - reg [BITS-1:0] mem [0:WORD_DEPTH-1]; - - integer j; - - always @(posedge clk) - begin - if (ce_rw1) - begin - {start_of_rw_p1} - if (corrupt_mem_on_X_p && - ((^we_in_rw1 === 1'bx) || (^addr_rw1 === 1'bx)) - ) - begin - // WEN or ADDR is unknown, so corrupt entire array (using unsynthesizeable for loop) - for (j = 0; j < WORD_DEPTH; j = j + 1) - mem[j] <= 'x; - end - else if (we_in_rw1) - begin - {byte_write_logic} - end - // read - {end_of_rw_p1} - end - else - begin - // Make sure read fails if ce_in is low - rd_out <= 'x; - end - end - - // Timing check placeholders (will be replaced during SDF back-annotation) - reg notifier; - specify - // Delay from clk to rd_out - (posedge clk *> rd_out_r1) = (0, 0); - - // Timing checks - $width (posedge clk, 0, 0, notifier); - $width (negedge clk, 0, 0, notifier); - $period (posedge clk, 0, notifier); -{setuphold_checks} - endspecify - -endmodule -''' -VLOG_BB_TEMPLATE_1r1w = '''\ -module {name} -( - clk, - rd_out_r1, - addr_r1, - addr_w1, - we_in_w1, - wd_in_w1, - w_mask_w1, - ce_r1, - ce_w1 -); - parameter BITS = {data_width}; - parameter WORD_DEPTH = {depth}; - parameter ADDR_WIDTH = {addr_width}; - parameter corrupt_mem_on_X_p = {crpt_on_x}; - - output reg [BITS-1:0] rd_out_r1; - input [ADDR_WIDTH-1:0] addr_r1; - input [ADDR_WIDTH-1:0] addr_w1; - input we_in_w1; - input [BITS-1:0] wd_in_w1; - input [BITS-1:0] w_mask_w1; - input clk; - input ce_r1; - input ce_w1; -endmodule''' - -VLOG_BB_TEMPLATE_2r1w = '''\ -module {name} -( - clk, - rd_out_r1, - rd_out_r2, - addr_r1, - addr_r2, - addr_w1, - we_in_w1, - wd_in_w1, - w_mask_w1, - ce_r1, - ce_r2, - ce_w1 -); - parameter BITS = {data_width}; - parameter WORD_DEPTH = {depth}; - parameter ADDR_WIDTH = {addr_width}; - parameter corrupt_mem_on_X_p = {crpt_on_x}; - - output reg [BITS-1:0] rd_out_r1; - output reg [BITS-1:0] rd_out_r2; - input [ADDR_WIDTH-1:0] addr_r1; - input [ADDR_WIDTH-1:0] addr_r2; - input [ADDR_WIDTH-1:0] addr_w; - input we_in_w1; - input [BITS-1:0] wd_in_w1; - input [BITS-1:0] w_mask_w1; - input clk; - input ce_r1; - input ce_r2; - input ce_w1; -endmodule''' - -VLOG_BB_TEMPLATE_1rw1r = '''\ -module {name} ( - // Port 0: RW (Write/Read Port) - clk0, - ce_rw1, - we_in_rw1, - w_mask_rw1, - addr_rw1, - wd_in_rw1, - rd_out_rw1, - // Port 1: R (Read-Only Port) - clk1, - ce_r1, - addr_r1, - rd_out_r1 -); - - parameter BITS = {data_width}; - parameter WORD_DEPTH = {depth}; - parameter ADDR_WIDTH = {addr_width}; - - - // Port 0: RW - input clk0; - input ce_rw1; - input we_in_rw1; - input [BITS-1:0] w_mask_rw1; - input [ADDR_WIDTH-1:0] addr_rw1; - input [BITS-1:0] wd_in_rw1; - output [BITS-1:0] rd_out_rw1; - - // Port 1: R - input clk1; - input ce_r1; - input [ADDR_WIDTH-1:0] addr_r1; - output [BITS-1:0] rd_out_r1; -endmodule -''' - -# Template for a verilog 1rw RAM interface -VLOG_BB_TEMPLATE_1rw = '''\ -module {name} -( - rd_out_rw1, - addr_rw1, - we_in_rw1, - wd_in_rw1, - w_mask_rw1, - clk, - ce_rw1 -); - parameter BITS = {data_width}; - parameter WORD_DEPTH = {depth}; - parameter ADDR_WIDTH = {addr_width}; - parameter corrupt_mem_on_X_p = {crpt_on_x}; - - output reg [BITS-1:0] rd_out_rw1; - input [ADDR_WIDTH-1:0] addr_rw1; - input we_in_rw1; - input [BITS-1:0] wd_in_rw1; - input [BITS-1:0] w_mask_rw1; - input clk; - input ce_rw1; - -endmodule -''' \ No newline at end of file + if type_port in ('w','rw'): + V_file.write(f' $setuphold (posedge {type_port}{i}_clk, {type_port}{i}_we_in, 0, 0, notifier);\n' + f' $setuphold (posedge {type_port}{i}_clk, {type_port}{i}_wd_in, 0, 0, notifier);\n') + if has_wmask: + V_file.write(f' $setuphold (posedge {type_port}{i}_clk, {type_port}{i}_wmask_in, 0, 0, notifier);\n') + + + V_file.write(f' $setuphold (posedge {type_port}{i}_clk, {type_port}{i}_ce_in, 0, 0, notifier);\n' + f' $setuphold (posedge {type_port}{i}_clk, {type_port}{i}_addr_in, 0, 0, notifier);\n' + f'\n') diff --git a/scripts/utils/mem_init/README.md b/scripts/utils/mem_init/README.md new file mode 100644 index 0000000..96552cd --- /dev/null +++ b/scripts/utils/mem_init/README.md @@ -0,0 +1,68 @@ +# Memory Initialization System + +A Python-based memory initialization system that integrates CACTI (Cache Access and Cycle Time Information) modeling with custom memory parameters to generate complete SRAM configurations. + +## Overview + +This system takes memory specifications and generates detailed memory objects with timing, power, and area characteristics. It supports three modes: +- **CACTI-only**: Uses CACTI for all parameters +- **Custom-only**: Uses only user-provided parameters +- **Hybrid**: Combines CACTI with custom overrides + +## Project Structure + +``` +utils/mem_init/ +├── mem_init.py # Main initialization entry point +├── mem_area.py # Area and dimension calculations +├── mem_globals.py # Global utilities and CACTI integration +└── modules/ # Core classes + ├── __init__.py # Module exports + ├── class_memory.py # Main Memory class + ├── class_cacti.py # CACTI data parsing + └── class_custom.py # Custom parameter handling +``` + +## Core Modules + +### Main Entry Point +- **`mem_init.py`** - Main memory initialization function + - Orchestrates the entire initialization process + - Sets up output directories and CACTI integration + - Handles mode selection (CACTI/Custom/Hybrid) + +### Area Calculations +- **`mem_area.py`** - Memory dimension and area calculations + - **`get_macro_dimensions()`** - Calculates memory height/width from bitcell parameters + - **`final_area()`** - Applies snapping to manufacturing grid and track pitch + - Handles banking and column multiplexing effects + +### Global Utilities +- **`mem_globals.py`** - Shared utilities and CACTI integration + - **`run_cacti()`** - Executes CACTI tool with generated config + - **`print_init_sram()`** - Comprehensive memory parameter summary + - **`round_up_to_multiple()`** - Precise decimal rounding for layout + - **`get_custom()`** - Parameter selection logic for hybrid mode + - Banking and column mux dimension helpers + +## Core Classes + +### Memory Class +- **`class_memory.py`** - Main memory configuration object + - Stores all memory parameters (width, depth, ports, timing) + - Calculates derived properties (address width, total size) + - Supports multiple port types (read, write, read-write) + - Handles write masking and granularity + +### CACTI Integration +- **`class_cacti.py`** - CACTI result parsing and processing + - **`CactiData`** - Parses CACTI CSV output into typed fields + - **`HybridData`** - Container for timing/power parameters in hybrid mode + - Extracts timing, power, and area data from CACTI results + +### Custom Parameters +- **`class_custom.py`** - User-defined memory parameters + - Technology parameters (fin pitch, poly pitch) + - Timing parameters (access time, cycle time) + - Power parameters (leakage, dynamic power) + - Area scaling factors for different port types diff --git a/scripts/utils/mem_init/mem_area.py b/scripts/utils/mem_init/mem_area.py new file mode 100644 index 0000000..e95fbaf --- /dev/null +++ b/scripts/utils/mem_init/mem_area.py @@ -0,0 +1,85 @@ +import os +import sys +import math +from utils.mem_init.mem_globals import * + + +def get_macro_dimensions(mem) -> float: + """DYNAMIC SIZING (preserve variable names; neutralize global port scaling) + Original multiplied whole macro by (r+w+rw) in x and y. + Keep xfactor/yfactor variables but set them to neutral (or asymmetric) scales.""" + if use_cacti(mem): + return mem.height_um, mem.width_um + + contacted_poly_pitch_um = mem.contacted_poly_pitch_nm / 1000.0 + fin_pitch_um = mem.finPitch_nm / 1000.0 + width_in_bits = int(mem.sram_data['width']) + depth = int(mem.sram_data['depth']) + num_banks = int(mem.sram_data['banks']) + h0_tracks = mem.h0_tracks or 1 + w0_polys = mem.w0_polys or 1 + dh_read = mem.dh_read or 1 + dw_read = mem.dw_read or 1 + dh_write = mem.dh_write or 1 + dw_write = mem.dw_write or 1 + dh_rw = mem.dh_rw or 1 + dw_rw = mem.dw_rw or 1 + + # Compute bitcell height/width in microns from tracks/pitches + h_tracks = h0_tracks + mem.r*dh_read + mem.w*dh_write + mem.rw*dh_rw + w_polys = w0_polys + mem.r*dw_read + mem.w*dw_write + mem.rw*dw_rw + + bitcell_height = h_tracks * fin_pitch_um + bitcell_width = w_polys * contacted_poly_pitch_um + + # rows reduced by mux + # columns increased by mux + all_bitcell_height = bitcell_height * depth + all_bitcell_width = bitcell_width * width_in_bits + + if num_banks == 2 or num_banks == 4: + all_bitcell_height, all_bitcell_width = get_bank_dimensions(mem, all_bitcell_height, all_bitcell_width) + elif num_banks != 1: + raise Exception("Unsupported number of banks: {}".format(num_banks)) + + # Same logic from FakeRAM2.0 + all_bitcell_height, all_bitcell_width = get_cmux_dimensions(mem, all_bitcell_height, all_bitcell_width) + + + total_height = all_bitcell_height * 1.2 + total_width = all_bitcell_width * 1.2 + return total_height, total_width + +def final_area(mem) -> float: + snapWidth_nm = mem.process.snapWidth_nm + snapHeight_nm = mem.process.snapHeight_nm + manufacturing_grid_nm = mem.process.manufacturing_grid_nm + x_pinPitch_um = mem.process.x_pinPitch_um + pinPitch_um = mem.process.pinPitch_um + + # TODO: + # Adjust to snap + # mem.width_um = (math.ceil((mem.width_um*1000.0)/snapWidth_nm)*snapWidth_nm)/1000.0 + # mem.height_um = (math.ceil((mem.height_um*1000.0)/snapHeight_nm)*snapHeight_nm)/1000.0 + + # Need to know the idea of snapping width and height + mem.width_um = round_up_to_multiple(mem.width_um*1000.0, snapWidth_nm) / 1000.0 + mem.height_um = round_up_to_multiple(mem.height_um*1000.0, snapHeight_nm) / 1000.0 + + mem.height_um = round_up_to_multiple(mem.height_um, manufacturing_grid_nm) + mem.width_um = round_up_to_multiple(mem.width_um, manufacturing_grid_nm) + + # TODO: snap to track pitch not pin pitch, + # Use pin pitch for now (assumes track pitch) + y_track_pitch = pinPitch_um + x_track_pitch = x_pinPitch_um + + mem.height_um = round_up_to_multiple(mem.height_um, y_track_pitch) + mem.width_um = round_up_to_multiple(mem.width_um, x_track_pitch) + + mem.area_um2 = mem.width_um * mem.height_um + + print(f'mem.width_um {mem.width_um}') + print(f'mem.height_um {mem.height_um}') + + return mem.width_um * mem.height_um \ No newline at end of file diff --git a/scripts/utils/mem_init/mem_globals.py b/scripts/utils/mem_init/mem_globals.py new file mode 100644 index 0000000..071cd29 --- /dev/null +++ b/scripts/utils/mem_init/mem_globals.py @@ -0,0 +1,170 @@ +import os +import math + +from utils.mem_init.modules import * +from decimal import Decimal, ROUND_UP +from utils.cacti_config import cacti_config + +# TODO: should also want to print summary of srams at end of run +def print_init_sram(gbl): + total_ports = gbl.r + gbl.w + gbl.rw + if total_ports == 0: + raise Exception("SRAM needs at least one port.") + print(f'\n ' + f'\n##################################################\n') + print(f'Creating SRAM: {gbl.name} of width: {gbl.width_in_bits} and depth: {gbl.depth}') + print('\n##################################################\n') + print(f'Total Ports : {total_ports} \n' + f'Num Port R : {gbl.r} \n' + f'Num Port W : {gbl.w} \n' + f'Num Port RW : {gbl.rw} \n' + f'Num Banks : {gbl.num_banks}\n' + ) + + if gbl.has_write_mask: + print( + f'Has write mask : True\n' + f'Num Port Wmask : {gbl.wmask}\n' + f'Write granularity : {gbl.write_granularity}\n' + ) + + print( + f'\nPIN PARAMETERS\n' + f'metLayerHorizontalPin : {gbl.process.metLayerHorizontalPin}\n' + f'metLayerVerticalPin : {gbl.process.metLayerVerticalPin}\n' + f'y_pinPitch_um : {gbl.process.y_pinPitch_um}\n' + f'x_pinPitch_um : {gbl.process.x_pinPitch_um}\n' + f'pinPitchFactor : {gbl.pinPitchFactor}\n' + f'y_pinOffset_um : {gbl.process.y_pinOffset_um}\n' + f'x_pinOffset_um : {gbl.process.x_pinOffset_um}\n' + f'x_pinWidth_um : {gbl.process.x_pinWidth_um}\n' + f'x_pinHeight_um : {gbl.process.x_pinHeight_um}\n' + f'y_pinWidth_um : {gbl.process.y_pinWidth_um}\n' + f'y_pinHeight_um : {gbl.process.y_pinHeight_um}\n' + f'\nPOWER GRID\n' + f'metLayerPowerGrid : {gbl.process.metLayerPowerGrid}\n' + f'directionPowerGrid : {gbl.process.directionPowerGrid}\n' + f'powerGridWidth_um : {gbl.process.powerGridWidth_um}\n' + f'powerGridPitch_um : {gbl.process.powerGridPitch_um}\n' + f'powerGridOffset_um : {gbl.process.powerGridOffset_um}\n' + + f'\nTIMING\n' + f't_setup_ns : {gbl.t_setup_ns}\n' + f't_hold_ns : {gbl.t_hold_ns}\n' + f'cap_input_pf : {gbl.cap_input_pf}\n' + + f'\nADDITIONAL PARAMS\n' + f'heightSnapPinPitch : {gbl.process.heightSnapPinPitch}\n' + f'widthSnapPinPitch : {gbl.process.widthSnapPinPitch}\n' + f'column_mux_factor overriden : {gbl.column_mux_factor_overriden}\n' + f'column_mux_factor : {gbl.process.column_mux_factor}\n' + f'snapWidth_um : {gbl.process.snapWidth_um}\n' + f'snapHeight_um : {gbl.process.snapHeight_um}\n' + f'\nUSE CUSTOM TECH: {gbl.process.use_custom_tech}\n' + ) + + if gbl.process.use_custom_tech == True: + print( + f'hybrid: : {gbl.custom_data.hybrid}\n' + f'use_custom_area : {gbl.custom_data.use_custom_area}\n' + f'transistor_architecture : {gbl.custom_data.transistor_architecture}\n' + f'access_time_ns : {gbl.custom_data.access_time_ns or gbl.access_time_ns}\n' + f'cycle_time_ns : {gbl.custom_data.cycle_time_ns or gbl.cycle_time_ns}\n' + f'fo4_ps : {gbl.custom_data.fo4_ps or gbl.fo4_ps}\n' + f'dyn_write_energy_nj : {gbl.dyn_write_energy_nj or None}\n' + f'standby_leakage_per_bank_mW : {gbl.custom_data.standby_leakage_per_bank_mW or gbl.standby_leakage_per_bank_mW}\n' + f'pin_dynamic_power_mW : {gbl.custom_data.pin_dynamic_power_mW or gbl.pin_dynamic_power_mW}\n' + f'contacted_poly_pitch_nm : {gbl.custom_data.contacted_poly_pitch_nm}\n' + f'finPitch_nm : {gbl.custom_data.finPitch_nm}\n' + f'h0_tracks : {gbl.custom_data.h0_tracks}\n' + f'w0_polys : {gbl.custom_data.w0_polys}\n' + f'dh_read : {gbl.custom_data.dh_read}\n' + f'dw_read : {gbl.custom_data.dw_read}\n' + f'dh_write : {gbl.custom_data.dh_write}\n' + f'dw_write : {gbl.custom_data.dw_write}\n' + f'dh_rw : {gbl.custom_data.dh_rw}\n' + f'dw_rw : {gbl.custom_data.dw_rw}\n\n') + print( + f'Total height : {gbl.height_um}\n' + f'Total width : {gbl.width_um}\n') + print('\n ') + +# def round_up_to_multiple(n, multiple) -> float: + # return math.ceil(n / multiple) * multiple + + +def round_up_to_multiple(n, multiple) -> float: + d_n = Decimal(str(n)) + d_multiple = Decimal(str(multiple)) + # Divide, round up, then multiply back + quotient = (d_n / d_multiple).to_integral_value(rounding=ROUND_UP) + result = quotient * d_multiple + return float(result) + +def get_custom(gbl, custom_param, cacti_data) -> float: + """ use cacti value of parameter if hybrid is not used, if hybrid + is true then either use cacti data or custom parameter if given """ + return cacti_data if gbl.custom_data.hybrid == False else (custom_param or cacti_data) + +def use_cacti(gbl) -> bool: + """ Only returns true if hybrid option is used """ + if gbl.process.use_custom_tech == False or (gbl.process.use_custom_tech == True and gbl.custom_data.hybrid == True): + print('Running Cacti with overidden values...\n') if gbl.custom_data.hybrid == True else print('Running Cacti...\n') + return True + print("Not using cacti...") + return False + +def run_cacti( gbl ) -> None: + fid = open(os.sep.join([gbl.results_dir,'cacti.cfg']), 'w') + fid.write( + cacti_config.format( + gbl.total_size, + gbl.width_in_bytes, + gbl.rw, + gbl.r, + gbl.w, + gbl.process.tech_um, + gbl.width_in_bytes * 8, + gbl.num_banks, + gbl.cache_type + ) + ) + fid.close() + odir = os.getcwd() + os.chdir(gbl.cacti_dir ) + cmd = os.sep.join(['.','cacti -infile ']) + os.sep.join([gbl.results_dir,'cacti.cfg']) + os.system( cmd) + os.chdir(odir) + +def get_cmux_dimensions(gbl, height_um, width_um) -> float: + """ Same column mux calculation logic from FakeRAM2.0 """ + height_um = height_um / gbl.column_mux_factor + width_um = width_um * gbl.column_mux_factor + return height_um, width_um + +def get_bank_dimensions(gbl, height_um, width_um) -> float: + """ Same bank calculation logic from FakeRAM2.0 """ + height_um = height_um / gbl.num_banks + width_um = width_um * gbl.num_banks + return height_um, width_um + +def get_write_granularity(gbl) -> int: + """ returns write granularity of sram """ + sram_data = gbl.sram_data + width_in_bits = gbl.width_in_bits + + if 'write_granularity' in sram_data and sram_data['write_granularity'] > 0: + gbl.has_write_mask = True + gbl.write_granularity = int(sram_data['write_granularity']) + else: + gbl.has_write_mask = False + gbl.write_granularity = width_in_bits + return + + if gbl.has_write_mask: + if (width_in_bits % gbl.write_granularity == 0): + return width_in_bits // gbl.write_granularity + else: + raise Exception(f"Invalid write_granularity: width_in_bits ({width_in_bits}) is not divisible by write_granularity ({gbl.write_granularity}).") + else: + return 0 # No write mask \ No newline at end of file diff --git a/scripts/utils/mem_init/mem_init.py b/scripts/utils/mem_init/mem_init.py new file mode 100644 index 0000000..93a8e79 --- /dev/null +++ b/scripts/utils/mem_init/mem_init.py @@ -0,0 +1,89 @@ + +import os + +from pathlib import Path +from utils.mem_init.modules import * +from utils.mem_init.mem_area import * +from utils.mem_init.mem_globals import * + + +def memory_initializer(mem: object, custom_data: dict, output_dir = None, cacti_dir = None) -> object: + + name = mem.name + + if output_dir: # Output dir was set by command line option + p = str(Path(output_dir).expanduser().resolve(strict=False)) + results_dir = os.sep.join([p, name]) + else: + results_dir = os.sep.join([os.getcwd(), 'results', name]) + if not os.path.exists( results_dir ): + os.makedirs( results_dir ) + + + # Setup CACTI directory + if cacti_dir: + cacti_dir = cacti_dir + else: + cacti_dir = os.environ['CACTI_BUILD_DIR'] + + mem.results_dir = results_dir + mem.cacti_dir = cacti_dir + """ First want to initialize custom data in case + custom data is used for a custom only or hybrid sram + configuration """ + custom_inst = CustomData(custom_data) + mem.custom_data = custom_inst + + if use_cacti(mem): + """_run_cacti: shell out to cacti to generate a csv file with more data + regarding this memory based on the input parameters from the json + configuration file.""" + run_cacti(mem) + + # Parse CACTI results + with open( os.sep.join([results_dir, 'cacti.cfg.out']), 'r' ) as fid: + lines = [line for line in fid] + cacti_data = lines[-1].split(',') + + cacti_inst = CactiData(mem, cacti_data) + + # Update mem object with cacti attributes + mem.__dict__.update(cacti_inst.__dict__) + + # TODO: modularize this somewhere + mem.height_um, mem.width_um = get_cmux_dimensions(mem, mem.height_um, mem.width_um) + mem.height_um, mem.width_um = get_bank_dimensions(mem, mem.height_um, mem.width_um) + + """ Cacti is ran and will be overriden by custom values """ + if custom_inst.hybrid == True: + hybrid_inst = HybridData( + access_time_ns = custom_inst.access_time_ns, + cycle_time_ns = custom_inst.cycle_time_ns, + pin_dynamic_power_mW = custom_inst.pin_dynamic_power_mW, + standby_leakage_per_bank_mW = custom_inst.standby_leakage_per_bank_mW, + fo4_ps = custom_inst.fo4_ps + ) + + # override cacti values from given custom data + hybrid_overrides = {k: v for k, v in hybrid_inst.__dict__.items() + if v is not None} + mem.__dict__.update(hybrid_overrides) + + else: + print('custom only') + # Update Memory class with custom data + mem.__dict__.update(custom_inst.__dict__) + + return mem_finalized_object(mem) + +def mem_finalized_object(mem: object): + """ inject additional variables here once main memory is initialized """ + + mem.wmask = get_write_granularity(mem) + + mem.height_um, mem.width_um = get_macro_dimensions(mem) + + mem.area_um = final_area(mem) + + + return mem \ No newline at end of file diff --git a/scripts/utils/mem_init/modules/__init__.py b/scripts/utils/mem_init/modules/__init__.py new file mode 100644 index 0000000..ba5b8e0 --- /dev/null +++ b/scripts/utils/mem_init/modules/__init__.py @@ -0,0 +1,4 @@ +from .class_memory import Memory +from .class_cacti import CactiData +from .class_cacti import HybridData +from .class_custom import CustomData diff --git a/scripts/utils/mem_init/modules/class_cacti.py b/scripts/utils/mem_init/modules/class_cacti.py new file mode 100644 index 0000000..2436c55 --- /dev/null +++ b/scripts/utils/mem_init/modules/class_cacti.py @@ -0,0 +1,59 @@ + +from dataclasses import dataclass +from utils.mem_init.mem_globals import * + +################################################################################ +# CACTI DATA CLASS +# +# Parses a CACTI result row into typed fields for later sizing/power logic. +# Includes a lightweight HybridData dataclass for aggregating timing/power. +# +# Dataclass: +# HybridData - timing & power summary container +# for custom values +################################################################################ + +tech_nm_idx = 0 +capacity_bytes_idx = 1 +associativity_idx = 2 +output_width_bits_idx = 3 +access_time_idx = 4 +cycle_time_ns_idx = 5 +dyn_search_energy_idx = 6 +dyn_read_energy_idx = 7 +dyn_write_energy_idx = 8 +standby_leakage_per_bank_idx = 9 +area_idx = 10 +fo4_idx = 11 +width_idx = 12 +height_idx = 13 + +@dataclass +class HybridData(): + access_time_ns : float = None + cycle_time_ns : float = None + pin_dynamic_power_mW : float = None + standby_leakage_per_bank_mW : float = None + fo4_ps : float = None + +class CactiData(): + def __init__(self, mem, cacti_data): + + # Initialize with CACTI data + self.tech_nm = int(cacti_data[tech_nm_idx]) + self.capacity_bytes = int(cacti_data[capacity_bytes_idx]) # Unused + self.associativity = int(cacti_data[associativity_idx]) # Unused + self.output_width_bits = int(cacti_data[output_width_bits_idx]) # Unused + self.access_time_ns = float(cacti_data[access_time_idx]) + self.cycle_time_ns = float(cacti_data[cycle_time_ns_idx]) + # self.dyn_search_energy_nj = float(cacti_data[dyn_search_energy_idx]) # Unused + self.dyn_read_energy_nj = float(cacti_data[dyn_read_energy_idx]) # Unused + self.dyn_write_energy_nj = float(cacti_data[dyn_write_energy_idx]) + self.standby_leakage_per_bank_mW = float(cacti_data[standby_leakage_per_bank_idx]) + self.area_mm2 = float(cacti_data[area_idx]) + self.fo4_ps = float(cacti_data[fo4_idx]) + self.width_um = float(cacti_data[width_idx]) + self.height_um = float(cacti_data[height_idx]) + + self.pin_dynamic_power_mW = self.dyn_write_energy_nj + diff --git a/scripts/utils/mem_init/modules/class_custom.py b/scripts/utils/mem_init/modules/class_custom.py new file mode 100644 index 0000000..c7be238 --- /dev/null +++ b/scripts/utils/mem_init/modules/class_custom.py @@ -0,0 +1,52 @@ +import sys + +from utils.mem_init.mem_area import get_macro_dimensions + +################################################################################ +# CUSTOM CLASS +# +# This class stores the infromation about the custom parameters that the memory +# is being generated in. +################################################################################ + +class CustomData(): + def __init__(self, custom_data): + if custom_data is not None: + self.hybrid = custom_data.get('hybrid', False) + self.transistor_architecture = str(custom_data.get('transistor_architecture', None)) + self.access_time_ns = float(custom_data.get('access_time_ns', 0.0)) + self.cycle_time_ns = float(custom_data.get('cycle_time_ns', 0.0)) + self.fo4_ps = float(custom_data.get('fo4_ps', 0.0)) + self.standby_leakage_per_bank_mW = float(custom_data.get('standby_leakage_per_bank_mW', 0.0)) + self.pin_dynamic_power_mW = float(custom_data.get('pin_dynamic_power_mW', 0.0)) + self.finPitch_nm = float(custom_data.get('finPitch_nm', 0.0)) + self.contacted_poly_pitch_nm = float(custom_data.get('contacted_poly_pitch_nm', 0.0)) + self.h0_tracks = float(custom_data.get('h0_tracks', 0.0)) + self.w0_polys = float(custom_data.get('w0_polys', 0.0)) + self.dh_read = float(custom_data.get('dh_read', 1)) + self.dw_read = float(custom_data.get('dw_read', 1)) + self.dh_write = float(custom_data.get('dh_write', 1)) + self.dw_write = float(custom_data.get('dw_write', 1)) + self.dh_rw = float(custom_data.get('dh_rw', 1)) + self.dw_rw = float(custom_data.get('dw_rw', 1)) + # TODO + self.use_custom_area = bool(custom_data.get('use_custom_area', False)) + else: + self.hybrid = False + self.use_custom_area = False + self.transistor_architecture = None + self.access_time_ns = 0.0 + self.cycle_time_ns = 0.0 + self.fo4_ps = 0.0 + self.standby_leakage_per_bank_mW = 0.0 + self.pin_dynamic_power_mW = 0.0 + self.finPitch_nm = 0.0 + self.contacted_poly_pitch_nm = 0.0 + self.h0_tracks = 0.0 + self.w0_polys = 0.0 + self.dh_read = 0.0 + self.dw_read = 0.0 + self.dh_write = 0.0 + self.dw_write = 0.0 + self.dh_rw = 0.0 + self.dw_rw = 0.0 diff --git a/scripts/utils/mem_init/modules/class_memory.py b/scripts/utils/mem_init/modules/class_memory.py new file mode 100644 index 0000000..c4b119c --- /dev/null +++ b/scripts/utils/mem_init/modules/class_memory.py @@ -0,0 +1,96 @@ +import math +from dataclasses import dataclass + +################################################################################ +# MEMORY CLASS +# +# This class stores the information about a specific memory that is being +# generated. This class takes in a process object, the information in one of +# the items in the "sram" list section of the json configuration file, and +# finally runs cacti to generate the rest of the data. +################################################################################ + +class Memory(): + def __init__( self, process, sram_data ): + self.process = process + self.sram_data = sram_data + self.name = str(sram_data.get('name' , None)) + self.width_in_bits = int(sram_data.get('width', None)) + self.depth = int(sram_data.get('depth', None)) + self.addr_width = math.ceil(math.log2(self.depth)) + self.width_in_bytes = math.ceil((self.width_in_bits / 8.0)) + self.total_size = self.width_in_bytes * self.depth + self.tech_node_um = self.process.tech_um + self.t_hold_ns = self.process.t_hold_ns + self.t_setup_ns = self.process.t_setup_ns + self.cap_input_pf = self.process.cap_input_pf + self.num_banks = int(sram_data.get('banks', 1)) + self.pinPitchFactor = int(sram_data.get('pinPitchFactor', 1)) + self.cache_type = str(sram_data.get('type','cache')) + self.write_mode = str(sram_data.get('write_mode','write_first')) + self.column_mux_factor_overriden = True if 'column_mux_factor' in sram_data else False + self.column_mux_factor = float(sram_data.get('column_mux_factor', self.process.column_mux_factor)) + self.r = int(sram_data['ports'].get('r', 0)) + self.w = int(sram_data['ports'].get('w', 0)) + self.rw = int(sram_data['ports'].get('rw', 0)) + self.has_write_mask = True if "write_granularity" in sram_data else False + self.fakeram_name_extension = 'fakeram.' if self.process.add_fakeram_extension == True else '' + self.write_granularity = None + self.results_dir = None + self.cacti_dir = None + self.area_mm2 = None + self.height_um = None + self.width_um = None + self.wmask = None + self.access_time_ns = None + self.cycle_time_ns = None + self.standby_leakage_per_bank_mW = None + self.fo4_ps = None + self.capacity_bytes = None + self.associativity = None + self.output_width_bits = None + self.dyn_search_energy_nj = None + self.dyn_read_energy_nj = None + self.dyn_write_energy_nj = None + self.pin_dynamic_power_mW = None + self.custom_data = None + self.finPitch_nm = None + self.contacted_poly_pitch_nm = None + self.h0_tracks = None + self.w0_polys = None + self.dh_read = None + self.dw_read = None + self.dh_write = None + self.dw_write = None + self.dh_rw = None + self.dw_rw = None + self.__post_init__() + + def __post_init__(self): + TimingData( + access_time_ns = self.access_time_ns, + cycle_time_ns = self.cycle_time_ns, + standby_leakage_per_bank_mW = self.standby_leakage_per_bank_mW, + fo4_ps = self.fo4_ps, + capacity_bytes = self.capacity_bytes, + associativity = self.associativity, + output_width_bits = self.output_width_bits, + dyn_search_energy_nj = self.dyn_search_energy_nj, + dyn_read_energy_nj = self.dyn_read_energy_nj, + dyn_write_energy_nj = self.dyn_write_energy_nj, + pin_dynamic_power_mW = self.pin_dynamic_power_mW + ) + +@dataclass +class TimingData(): + access_time_ns : float + cycle_time_ns : float + standby_leakage_per_bank_mW : float + fo4_ps : float + capacity_bytes : float + associativity : float + output_width_bits : float + dyn_search_energy_nj : float + dyn_read_energy_nj : float + dyn_write_energy_nj : float + pin_dynamic_power_mW : float