From 65c5cc9fe72ab9028c7bab6a2fdba2e68aea62bf Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Thu, 24 Jan 2019 07:09:51 -0800
Subject: [PATCH 01/35] added support for more corner variations
---
compiler/datasheet/datasheet_gen.py | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index 6786c7bf..618629ff 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -21,12 +21,27 @@ def process_name(corner):
"""
Expands the names of the characterization corner types into something human friendly
"""
+ if corner == "TS":
+ return "Typical - Slow"
if corner == "TT":
return "Typical - Typical"
+ if corner == "TF":
+ return "Typical - Fast"
+
if corner == "SS":
return "Slow - Slow"
+ if corner == "ST":
+ return "Slow - Typical"
+ if corner == "SF":
+ return "Slow - Fast"
+
+ if corner == "FS":
+ return "Fast - Slow"
+ if corner == "FT":
+ return "Fast - Typical"
if corner == "FF":
return "Fast - Fast"
+
else:
return "custom"
From ed901aba5f9f34fc4b6f3b98c255df584394ef8c Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Mon, 28 Jan 2019 10:29:27 -0800
Subject: [PATCH 02/35] changed datetime to date
---
compiler/characterizer/lib.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py
index 156481ce..93592882 100644
--- a/compiler/characterizer/lib.py
+++ b/compiler/characterizer/lib.py
@@ -525,7 +525,7 @@ class lib:
datasheet = open(OPTS.openram_temp +'/datasheet.info', 'a+')
- current_time = datetime.datetime.now()
+ current_time = datetime.date.today()
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},".format(
"sram_{0}_{1}_{2}".format(OPTS.word_size, OPTS.num_words, OPTS.tech_name),
OPTS.num_words,
From 475db65d267fbf9c293ccf520949905051b70b64 Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Wed, 30 Jan 2019 17:49:43 -0800
Subject: [PATCH 03/35] added units to AREA on datasheet
---
compiler/datasheet/datasheet_gen.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index 618629ff..ad14801b 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -552,7 +552,7 @@ def parse_characterizer_csv(f, pages):
new_sheet.io_table.add_row(['NUM_RW_PORTS', NUM_RW_PORTS])
new_sheet.io_table.add_row(['NUM_R_PORTS', NUM_R_PORTS])
new_sheet.io_table.add_row(['NUM_W_PORTS', NUM_W_PORTS])
- new_sheet.io_table.add_row(['Area', AREA])
+ new_sheet.io_table.add_row(['Area (µm2)', AREA])
class datasheet_gen():
From 21868e1b60cd8cd1f468f45ef7c6132e54f70563 Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Thu, 31 Jan 2019 08:09:00 -0800
Subject: [PATCH 04/35] removed expanded process names from corners
---
compiler/characterizer/lib.py | 2 +-
compiler/datasheet/datasheet_gen.py | 67 ++-
compiler/datasheet/wavedrom.py | 827 ----------------------------
3 files changed, 34 insertions(+), 862 deletions(-)
delete mode 100644 compiler/datasheet/wavedrom.py
diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py
index 93592882..a38ecd93 100644
--- a/compiler/characterizer/lib.py
+++ b/compiler/characterizer/lib.py
@@ -527,7 +527,7 @@ class lib:
current_time = datetime.date.today()
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},".format(
- "sram_{0}_{1}_{2}".format(OPTS.word_size, OPTS.num_words, OPTS.tech_name),
+ OPTS.output_name,
OPTS.num_words,
OPTS.num_banks,
OPTS.num_rw_ports,
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index ad14801b..24bc3953 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -16,34 +16,34 @@ import csv
import datasheet
import table_gen
-
-def process_name(corner):
- """
- Expands the names of the characterization corner types into something human friendly
- """
- if corner == "TS":
- return "Typical - Slow"
- if corner == "TT":
- return "Typical - Typical"
- if corner == "TF":
- return "Typical - Fast"
-
- if corner == "SS":
- return "Slow - Slow"
- if corner == "ST":
- return "Slow - Typical"
- if corner == "SF":
- return "Slow - Fast"
-
- if corner == "FS":
- return "Fast - Slow"
- if corner == "FT":
- return "Fast - Typical"
- if corner == "FF":
- return "Fast - Fast"
-
- else:
- return "custom"
+# def process_name(corner):
+# """
+# Expands the names of the characterization corner types into something human friendly
+# """
+# if corner == "TS":
+# return "Typical - Slow"
+# if corner == "TT":
+# return "Typical - Typical"
+# if corner == "TF":
+# return "Typical - Fast"
+#
+# if corner == "SS":
+# return "Slow - Slow"
+# if corner == "ST":
+# return "Slow - Typical"
+# if corner == "SF":
+# return "Slow - Fast"
+#
+# if corner == "FS":
+# return "Fast - Slow"
+# if corner == "FT":
+# return "Fast - Typical"
+# if corner == "FF":
+# return "Fast - Fast"
+#
+# else:
+# return "custom"
+#
def parse_characterizer_csv(f, pages):
@@ -351,8 +351,7 @@ def parse_characterizer_csv(f, pages):
sheet.description.append(str(element))
break
- new_sheet.corners_table.add_row([PROC, process_name(
- PROC), VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
+ new_sheet.corners_table.add_row([PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
new_sheet.dlv_table.add_row(
['.lib', 'Synthesis models', '{1}'.format(LIB_NAME, LIB_NAME.replace(OUT_DIR, ''))])
@@ -371,9 +370,8 @@ def parse_characterizer_csv(f, pages):
new_sheet.corners_table = table_gen.table_gen("corners")
new_sheet.corners_table.add_row(
- ['Corner Name', 'Process', 'Power Supply', 'Temperature', 'Library Name Suffix'])
- new_sheet.corners_table.add_row([PROC, process_name(
- PROC), VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
+ ['Transistor Type', 'Power Supply', 'Temperature', 'Corner Name'])
+ new_sheet.corners_table.add_row([PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
new_sheet.operating_table = table_gen.table_gen(
"operating_table")
new_sheet.operating_table.add_row(
@@ -552,7 +550,8 @@ def parse_characterizer_csv(f, pages):
new_sheet.io_table.add_row(['NUM_RW_PORTS', NUM_RW_PORTS])
new_sheet.io_table.add_row(['NUM_R_PORTS', NUM_R_PORTS])
new_sheet.io_table.add_row(['NUM_W_PORTS', NUM_W_PORTS])
- new_sheet.io_table.add_row(['Area (µm2)', AREA])
+ new_sheet.io_table.add_row(
+ ['Area (µm2)', AREA])
class datasheet_gen():
diff --git a/compiler/datasheet/wavedrom.py b/compiler/datasheet/wavedrom.py
deleted file mode 100644
index e8c68c56..00000000
--- a/compiler/datasheet/wavedrom.py
+++ /dev/null
@@ -1,827 +0,0 @@
-#!/usr/bin/python
-# The MIT License (MIT)
-#
-# Copyright (c) 2011-2016 Aliaksei Chapyzhenka
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# Translated to Python from original file:
-# https://github.com/drom/wavedrom/blob/master/src/WaveDrom.js
-#
-
-import sys
-import json
-import math
-import waveskin
-
-font_width = 7
-
-lane = {
- "xs" : 20, # tmpgraphlane0.width
- "ys" : 20, # tmpgraphlane0.height
- "xg" : 120, # tmpgraphlane0.x
- "yg" : 0, # head gap
- "yh0" : 0, # head gap title
- "yh1" : 0, # head gap
- "yf0" : 0, # foot gap
- "yf1" : 0, # foot gap
- "y0" : 5, # tmpgraphlane0.y
- "yo" : 30, # tmpgraphlane1.y - y0
- "tgo" : -10, # tmptextlane0.x - xg
- "ym" : 15, # tmptextlane0.y - y0
- "xlabel" : 6, # tmptextlabel.x - xg
- "xmax" : 1,
- "scale" : 1,
- "head" : {},
- "foot" : {}
-}
-
-def genBrick (texts, extra, times) :
-
- R = []
- if len( texts ) == 4 :
- for j in range( times ):
-
- R.append(texts[0])
-
- for i in range ( extra ):
- R.append(texts[1])
-
- R.append(texts[2])
- for i in range ( extra ):
- R.append(texts[3])
-
- return R
-
- if len( texts ) == 1 :
- texts.append(texts[0])
-
- R.append(texts[0])
- for i in range (times * (2 * (extra + 1)) - 1) :
- R.append(texts[1])
- return R
-
-def genFirstWaveBrick (text, extra, times) :
-
- pattern = {
- 'p': ['pclk', '111', 'nclk', '000'],
- 'n': ['nclk', '000', 'pclk', '111'],
- 'P': ['Pclk', '111', 'nclk', '000'],
- 'N': ['Nclk', '000', 'pclk', '111'],
- 'l': ['000'],
- 'L': ['000'],
- '0': ['000'],
- 'h': ['111'],
- 'H': ['111'],
- '1': ['111'],
- '=': ['vvv-2'],
- '2': ['vvv-2'],
- '3': ['vvv-3'],
- '4': ['vvv-4'],
- '5': ['vvv-5'],
- 'd': ['ddd'],
- 'u': ['uuu'],
- 'z': ['zzz']
- }
-
- return genBrick( pattern.get( text, ['xxx'] ) , extra, times );
-
-def genWaveBrick (text, extra, times) :
-
- x1 = {'p':'pclk', 'n':'nclk', 'P':'Pclk', 'N':'Nclk', 'h':'pclk', 'l':'nclk', 'H':'Pclk', 'L':'Nclk'}
- x2 = {'0':'0', '1':'1', 'x':'x', 'd':'d', 'u':'u', 'z':'z', '=':'v', '2':'v', '3':'v', '4':'v', '5':'v' }
- x3 = {'0': '', '1': '', 'x': '', 'd': '', 'u': '', 'z': '', '=':'-2', '2':'-2', '3':'-3', '4':'-4', '5':'-5'}
- y1 = {
- 'p':'0', 'n':'1',
- 'P':'0', 'N':'1',
- 'h':'1', 'l':'0',
- 'H':'1', 'L':'0',
- '0':'0', '1':'1', 'x':'x', 'd':'d', 'u':'u', 'z':'z', '=':'v', '2':'v', '3':'v', '4':'v', '5':'v'}
-
- y2 = {
- 'p': '', 'n': '',
- 'P': '', 'N': '',
- 'h': '', 'l': '',
- 'H': '', 'L': '',
- '0': '', '1': '', 'x': '', 'd': '', 'u': '', 'z': '', '=':'-2', '2':'-2', '3':'-3', '4':'-4', '5':'-5'}
-
- x4 = {
- 'p': '111', 'n': '000',
- 'P': '111', 'N': '000',
- 'h': '111', 'l': '000',
- 'H': '111', 'L': '000',
- '0': '000', '1': '111', 'x': 'xxx', 'd': 'ddd', 'u': 'uuu', 'z': 'zzz',
- '=': 'vvv-2', '2': 'vvv-2', '3': 'vvv-3', '4': 'vvv-4', '5': 'vvv-5'}
-
- x5 = {'p':'nclk', 'n':'pclk', 'P':'nclk', 'N':'pclk'}
- x6 = {'p': '000', 'n': '111', 'P': '000', 'N': '111'}
- xclude = {'hp':'111', 'Hp':'111', 'ln': '000', 'Ln': '000', 'nh':'111', 'Nh':'111', 'pl': '000', 'Pl':'000'}
-
- #atext = text.split()
- atext = text
-
- tmp0 = x4.get(atext[1])
- tmp1 = x1.get(atext[1])
- if tmp1 == None :
- tmp2 = x2.get(atext[1])
- if tmp2 == None :
- # unknown
- return genBrick(['xxx'], extra, times)
- else :
- tmp3 = y1.get(atext[0])
- if tmp3 == None :
- # unknown
- return genBrick(['xxx'], extra, times)
-
- # soft curves
- return genBrick([tmp3 + 'm' + tmp2 + y2[atext[0]] + x3[atext[1]], tmp0], extra, times)
-
- else :
- tmp4 = xclude.get(text)
- if tmp4 != None :
- tmp1 = tmp4
-
- # sharp curves
- tmp2 = x5.get(atext[1])
- if tmp2 == None :
- # hlHL
- return genBrick([tmp1, tmp0], extra, times)
- else :
- # pnPN
- return genBrick([tmp1, tmp0, tmp2, x6[atext[1]]], extra, times)
-
-def parseWaveLane (text, extra) :
-
- R = []
- Stack = text
- Next = Stack[0]
- Stack = Stack[1:]
-
- Repeats = 1
- while len(Stack) and ( Stack[0] == '.' or Stack[0] == '|' ): # repeaters parser
- Stack=Stack[1:]
- Repeats += 1
-
- R.extend(genFirstWaveBrick(Next, extra, Repeats))
-
- while len(Stack) :
- Top = Next
- Next = Stack[0]
- Stack = Stack[1:]
- Repeats = 1
- while len(Stack) and ( Stack[0] == '.' or Stack[0] == '|' ) : # repeaters parser
- Stack=Stack[1:]
- Repeats += 1
- R.extend(genWaveBrick((Top + Next), extra, Repeats))
-
- for i in range( lane['phase'] ):
- R = R[1:]
- return R
-
-def parseWaveLanes (sig) :
-
- def data_extract (e) :
- tmp = e.get('data')
- if tmp == None : return None
- if is_type_str (tmp) : tmp=tmp.split()
- return tmp
-
- content = []
- for sigx in sig :
- lane['period'] = sigx.get('period',1)
- lane['phase'] = int( sigx.get('phase',0 ) * 2 )
- sub_content=[]
- sub_content.append( [sigx.get('name',' '), sigx.get('phase',0 ) ] )
- sub_content.append( parseWaveLane( sigx['wave'], int(lane['period'] * lane['hscale'] - 1 ) ) if sigx.get('wave') else None )
- sub_content.append( data_extract(sigx) )
- content.append(sub_content)
-
- return content
-
-def findLaneMarkers (lanetext) :
-
- lcount = 0
- gcount = 0
- ret = []
- for i in range( len( lanetext ) ) :
- if lanetext[i] == 'vvv-2' or lanetext[i] == 'vvv-3' or lanetext[i] == 'vvv-4' or lanetext[i] == 'vvv-5' :
- lcount += 1
- else :
- if lcount !=0 :
- ret.append(gcount - ((lcount + 1) / 2))
- lcount = 0
-
- gcount += 1
-
- if lcount != 0 :
- ret.append(gcount - ((lcount + 1) / 2))
-
- return ret
-
-def renderWaveLane (root, content, index) :
-
- xmax = 0
- xgmax = 0
- glengths = []
- svgns = 'http://www.w3.org/2000/svg'
- xlinkns = 'http://www.w3.org/1999/xlink'
- xmlns = 'http://www.w3.org/XML/1998/namespace'
- for j in range( len(content) ):
- name = content[j][0][0]
- if name : # check name
- g = [
- 'g',
- {
- 'id': 'wavelane_' + str(j) + '_' + str(index),
- 'transform': 'translate(0,' + str(lane['y0'] + j * lane['yo']) + ')'
- }
- ]
- root.append(g)
- title = [
- 'text',
- {
- 'x': lane['tgo'],
- 'y': lane['ym'],
- 'class': 'info',
- 'text-anchor': 'end',
- 'xml:space': 'preserve'
- },
- ['tspan', name]
- ]
- g.append(title)
-
- glengths.append( len(name) * font_width + font_width )
-
- xoffset = content[j][0][1]
- xoffset = math.ceil(2 * xoffset) - 2 * xoffset if xoffset > 0 else -2 * xoffset
- gg = [
- 'g',
- {
- 'id': 'wavelane_draw_' + str(j) + '_' + str(index),
- 'transform': 'translate(' + str( xoffset * lane['xs'] ) + ', 0)'
- }
- ]
- g.append(gg)
-
- if content[j][1] :
- for i in range( len(content[j][1]) ) :
- b = [
- 'use',
- {
- #'id': 'use_' + str(i) + '_' + str(j) + '_' + str(index),
- 'xmlns:xlink':xlinkns,
- 'xlink:href': '#' + str( content[j][1][i] ),
- 'transform': 'translate(' + str(i * lane['xs']) + ')'
- }
- ]
- gg.append(b)
-
- if content[j][2] and len(content[j][2]) :
- labels = findLaneMarkers(content[j][1])
- if len(labels) != 0 :
- for k in range( len(labels) ) :
- if content[j][2] and k < len(content[j][2]) :
- title = [
- 'text',
- {
- 'x': int(labels[k]) * lane['xs'] + lane['xlabel'],
- 'y': lane['ym'],
- 'text-anchor': 'middle',
- 'xml:space': 'preserve'
- },
- ['tspan',content[j][2][k]]
- ]
- gg.append(title)
-
-
- if len(content[j][1]) > xmax :
- xmax = len(content[j][1])
-
- lane['xmax'] = xmax
- lane['xg'] = xgmax + 20
- return glengths
-
-def renderMarks (root, content, index) :
-
- def captext ( g, cxt, anchor, y ) :
-
- if cxt.get(anchor) and cxt[anchor].get('text') :
- tmark = [
- 'text',
- {
- 'x': float( cxt['xmax'] ) * float( cxt['xs'] ) / 2,
- 'y': y,
- 'text-anchor': 'middle',
- 'fill': '#000',
- 'xml:space': 'preserve'
- }, cxt[anchor]['text']
- ]
- g.append(tmark)
-
- def ticktock ( g, cxt, ref1, ref2, x, dx, y, length ) :
- L = []
-
- if cxt.get(ref1) == None or cxt[ref1].get(ref2) == None :
- return
-
- val = cxt[ref1][ref2]
- if is_type_str( val ) :
- val = val.split()
- elif type( val ) is int :
- offset = val
- val = []
- for i in range ( length ) :
- val.append(i + offset)
-
- if type( val ) is list :
- if len( val ) == 0 :
- return
- elif len( val ) == 1 :
- offset = val[0]
- if is_type_str(offset) :
- L = val
- else :
- for i in range ( length ) :
- L[i] = i + offset
-
- elif len( val ) == 2:
- offset = int(val[0])
- step = int(val[1])
- tmp = val[1].split('.')
- if len( tmp ) == 2 :
- dp = len( tmp[1] )
-
- if is_type_str(offset) or is_type_str(step) :
- L = val
- else :
- offset = step * offset
- for i in range( length ) :
- L[i] = "{0:.",dp,"f}".format(step * i + offset)
-
- else :
- L = val
-
- else :
- return
-
- for i in range( length ) :
- tmp = L[i]
- tmark = [
- 'text',
- {
- 'x': i * dx + x,
- 'y': y,
- 'text-anchor': 'middle',
- 'class': 'muted',
- 'xml:space': 'preserve'
- }, str(tmp)
- ]
- g.append(tmark)
-
- mstep = 2 * int(lane['hscale'])
- mmstep = mstep * lane['xs']
- marks = int( lane['xmax'] / mstep )
- gy = len( content ) * int(lane['yo'])
-
- g = ['g', {'id': 'gmarks_' + str(index)}]
- root.insert(0,g)
-
- for i in range( marks + 1):
- gg = [
- 'path',
- {
- 'id': 'gmark_' + str(i) + '_' + str(index),
- 'd': 'm ' + str(i * mmstep) + ',' + '0' + ' 0,' + str(gy),
- 'style': 'stroke:#888;stroke-width:0.5;stroke-dasharray:1,3'
- }
- ]
- g.append( gg )
-
- captext(g, lane, 'head', -33 if lane['yh0'] else -13 )
- captext(g, lane, 'foot', gy + ( 45 if lane['yf0'] else 25 ) )
-
- ticktock( g, lane, 'head', 'tick', 0, mmstep, -5, marks + 1)
- ticktock( g, lane, 'head', 'tock', mmstep / 2, mmstep, -5, marks)
- ticktock( g, lane, 'foot', 'tick', 0, mmstep, gy + 15, marks + 1)
- ticktock( g, lane, 'foot', 'tock', mmstep / 2, mmstep, gy + 15, marks)
-
-def renderArcs (root, source, index, top) :
-
- Stack = []
- Edge = {'words': [], 'frm': 0, 'shape': '', 'to': 0, 'label': ''}
- Events = {}
- svgns = 'http://www.w3.org/2000/svg'
- xmlns = 'http://www.w3.org/XML/1998/namespace'
-
- if source :
- for i in range (len (source) ) :
- lane['period'] = source[i].get('period',1)
- lane['phase'] = int( source[i].get('phase',0 ) * 2 )
- text = source[i].get('node')
- if text:
- Stack = text
- pos = 0
- while len( Stack ) :
- eventname = Stack[0]
- Stack=Stack[1:]
- if eventname != '.' :
- Events[eventname] = {
- 'x' : str( int( float( lane['xs'] ) * (2 * pos * lane['period'] * lane['hscale'] - lane['phase'] ) + float( lane['xlabel'] ) ) ),
- 'y' : str( int( i * lane['yo'] + lane['y0'] + float( lane['ys'] ) * 0.5 ) )
- }
- pos += 1
-
- gg = [ 'g', { 'id' : 'wavearcs_' + str( index ) } ]
- root.append(gg)
-
- if top.get('edge') :
- for i in range( len ( top['edge'] ) ) :
- Edge['words'] = top['edge'][i].split()
- Edge['label'] = top['edge'][i][len(Edge['words'][0]):]
- Edge['label'] = Edge['label'][1:]
- Edge['frm'] = Edge['words'][0][0]
- Edge['to'] = Edge['words'][0][-1]
- Edge['shape'] = Edge['words'][0][1:-1]
- frm = Events[Edge['frm']]
- to = Events[Edge['to']]
- gmark = [
- 'path',
- {
- 'id': 'gmark_' + Edge['frm'] + '_' + Edge['to'],
- 'd': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + to['x'] + ',' + to['y'],
- 'style': 'fill:none;stroke:#00F;stroke-width:1'
- }
- ]
- gg.append(gmark)
- dx = float( to['x'] ) - float( frm['x'] )
- dy = float( to['y'] ) - float( frm['y'] )
- lx = (float(frm['x']) + float(to['x'])) / 2
- ly = (float(frm['y']) + float(to['y'])) / 2
- pattern = {
- '~' : {'d': 'M ' + frm['x'] + ',' + frm['y'] + ' c ' + str(0.7 * dx) + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy) },
- '-~' : {'d': 'M ' + frm['x'] + ',' + frm['y'] + ' c ' + str(0.7 * dx) + ', 0 ' + str(dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy) },
- '~-' : {'d': 'M ' + frm['x'] + ',' + frm['y'] + ' c ' + '0' + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy) },
- '-|' : {'d': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx) + ',0 0,' + str(dy)},
- '|-' : {'d': 'm ' + frm['x'] + ',' + frm['y'] + ' 0,' + str(dy) + ' ' + str(dx) + ',0'},
- '-|-' : {'d': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx / 2) + ',0 0,' + str(dy) + ' ' + str(dx / 2) + ',0'},
- '->' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none'},
- '~>' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + str(0.7 * dx) + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
- '-~>' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + str(0.7 * dx) + ', 0 ' + str(dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
- '~->' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + '0' + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
- '-|>' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx) + ',0 0,' + str(dy)},
- '|->' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'm ' + frm['x'] + ',' + frm['y'] + ' 0,' + str(dy) + ' ' + str(dx) + ',0'},
- '-|->' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx / 2) + ',0 0,' + str(dy) + ' ' + str(dx / 2) + ',0'},
- '<->' : {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none'},
- '<~>' : {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none','d': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + str(0.7 * dx) + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
- '<-~>' : {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none','d': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + str(0.7 * dx) + ', 0 ' + str(dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
- '<-|>' : {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none','d': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx) + ',0 0,' + str(dy)},
- '<-|->': {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none','d': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx / 2) + ',0 0,' + str(dy) + ' ' + str(dx / 2) + ',0'}
- }
- gmark[1].update( pattern.get( Edge['shape'], { 'style': 'fill:none;stroke:#00F;stroke-width:1' } ) )
-
- if Edge['label']:
- if Edge['shape'] == '-~' :
- lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.75
- if Edge['shape'] == '~-' :
- lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.25
- if Edge['shape'] == '-|' :
- lx = float(to['x'])
- if Edge['shape'] == '|-' :
- lx = float(frm['x'])
- if Edge['shape'] == '-~>':
- lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.75
- if Edge['shape'] == '~->':
- lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.25
- if Edge['shape'] == '-|>' :
- lx = float(to['x'])
- if Edge['shape'] == '|->' :
- lx = float(frm['x'])
- if Edge['shape'] == '<-~>':
- lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.75
- if Edge['shape'] =='<-|>' :
- lx = float(to['x'])
-
- lwidth = len( Edge['label'] ) * font_width
- label = [
- 'text',
- {
- 'style': 'font-size:10px;',
- 'text-anchor': 'middle',
- 'xml:space': 'preserve',
- 'x': int( lx ),
- 'y': int( ly + 3 )
- },
- [ 'tspan', Edge['label'] ]
- ]
- underlabel = [
- 'rect',
- {
- 'height': 9,
- 'style': 'fill:#FFF;',
- 'width': lwidth,
- 'x': int( lx - lwidth / 2 ),
- 'y': int( ly - 5 )
- }
- ]
- gg.append(underlabel)
- gg.append(label)
-
- for k in Events:
- if k.islower() :
- if int( Events[k]['x'] ) > 0 :
- lwidth = len( k ) * font_width
- underlabel = [
- 'rect',
- {
- 'x': float( Events[k]['x'] ) - float(lwidth) / 2,
- 'y': int( Events[k]['y'] ) - 4,
- 'height': 8,
- 'width': lwidth,
- 'style': 'fill:#FFF;'
- }
- ]
- gg.append(underlabel)
- label = [
- 'text',
- {
- 'style': 'font-size:8px;',
- 'x': int( Events[k]['x'] ),
- 'y': int( Events[k]['y'] ) + 2,
- 'width': lwidth,
- 'text-anchor': 'middle'
- },
- k
- ]
- gg.append(label)
-
-def parseConfig (source) :
-
- lane['hscale'] = 1
- if lane.get('hscale0') :
- lane['hscale'] = lane['hscale0']
-
- if source and source.get('config') and source.get('config').get('hscale'):
- hscale = round(source.get('config').get('hscale'))
- if hscale > 0 :
- if hscale > 100 : hscale = 100
- lane['hscale'] = hscale
-
- lane['yh0'] = 0
- lane['yh1'] = 0
- if source and source.get('head') :
- lane['head'] = source['head']
- if source.get('head').get('tick',0) == 0 : lane['yh0'] = 20
- if source.get('head').get('tock',0) == 0 : lane['yh0'] = 20
- if source.get('head').get('text') : lane['yh1'] = 46; lane['head']['text'] = source['head']['text']
-
- lane['yf0'] = 0
- lane['yf1'] = 0
- if source and source.get('foot') :
- lane['foot'] = source['foot']
- if source.get('foot').get('tick',0) == 0 : lane['yf0'] = 20
- if source.get('foot').get('tock',0) == 0 : lane['yf0'] = 20
- if source.get('foot').get('text') : lane['yf1'] = 46; lane['foot']['text'] = source['foot']['text']
-
-def rec (tmp, state) :
-
- name = str( tmp[0] )
- delta_x = 25
-
- state['x'] += delta_x
- for i in range( len( tmp ) ) :
- if type( tmp[i] ) is list :
- old_y = state['y']
- rec( tmp[i], state )
- state['groups'].append( {'x':state['xx'], 'y':old_y, 'height':state['y'] - old_y, 'name': state['name'] } )
- elif type( tmp[i] ) is dict :
- state['lanes'].append(tmp[i])
- state['width'].append(state['x'])
- state['y'] += 1
-
- state['xx'] = state['x']
- state['x'] -= delta_x
- state['name'] = name
-
-def insertSVGTemplate (index, parent, source) :
-
- e = waveskin.WaveSkin['default']
-
- if source.get('config') and source.get('config').get('skin') :
- if waveskin.WaveSkin.get( source.get('config').get('skin') ) :
- e = waveskin.WaveSkin[ source.get('config').get('skin') ]
-
- if index == 0 :
- lane['xs'] = int( e[3][1][2][1]['width'] )
- lane['ys'] = int( e[3][1][2][1]['height'] )
- lane['xlabel'] = int( e[3][1][2][1]['x'] )
- lane['ym'] = int( e[3][1][2][1]['y'] )
-
- else :
- e = ['svg', {'id': 'svg', 'xmlns': 'http://www.w3.org/2000/svg', 'xmlns:xlink': 'http://www.w3.org/1999/xlink', 'height': '0'},
- ['g', {'id': 'waves'},
- ['g', {'id': 'lanes'}],
- ['g', {'id': 'groups'}]
- ]
- ]
-
- e[-1][1]['id'] = 'waves_' + str(index)
- e[-1][2][1]['id'] = 'lanes_' + str(index)
- e[-1][3][1]['id'] = 'groups_' + str(index)
- e[1]['id'] = 'svgcontent_' + str(index)
- e[1]['height'] = 0
-
- parent.extend(e)
-
-def renderWaveForm (index, source, output) :
-
- xmax = 0
- root = []
- groups = []
-
- if source.get('signal'):
- insertSVGTemplate(index, output, source)
- parseConfig( source )
- ret = {'x':0, 'y':0, 'xmax':0, 'width':[], 'lanes':[], 'groups':[] }
- rec( source['signal'], ret )
- content = parseWaveLanes(ret['lanes'])
- glengths = renderWaveLane(root, content, index)
- for i in range( len( glengths ) ):
- xmax = max( xmax, ( glengths[i] + ret['width'][i] ) )
- renderMarks(root, content, index)
- renderArcs(root, ret['lanes'], index, source)
- renderGaps(root, ret['lanes'], index)
- renderGroups(groups, ret['groups'], index)
- lane['xg'] = int( math.ceil( float( xmax - lane['tgo'] ) / float(lane['xs'] ) ) ) * lane['xs']
- width = (lane['xg'] + lane['xs'] * (lane['xmax'] + 1) )
- height = len(content) * lane['yo'] + lane['yh0'] + lane['yh1'] + lane['yf0'] + lane['yf1']
- output[1]={
- 'id' :'svgcontent_' + str(index),
- 'xmlns' :"http://www.w3.org/2000/svg",
- 'xmlns:xlink':"http://www.w3.org/1999/xlink",
- 'width' :str(width),
- 'height' :str(height),
- 'viewBox' :'0 0 ' + str(width) + ' ' + str(height),
- 'overflow' :"hidden"
- }
- output[-1][2][1]['transform']='translate(' + str(lane['xg'] + 0.5) + ', ' + str((float(lane['yh0']) + float(lane['yh1'])) + 0.5) + ')'
-
- output[-1][2].extend(root)
- output[-1][3].extend(groups)
-
-def renderGroups (root, groups, index) :
-
- svgns = 'http://www.w3.org/2000/svg',
- xmlns = 'http://www.w3.org/XML/1998/namespace'
-
- for i in range( len( groups ) ) :
- group = [
- 'path',
- {
- 'id': 'group_' + str(i) + '_' + str(index),
- 'd': 'm ' + str( groups[i]['x'] + 0.5 ) + ',' + str( groups[i]['y']* lane['yo'] + 3.5 + lane['yh0'] + lane['yh1'] ) + ' c -3,0 -5,2 -5,5 l 0,' + str( int( groups[i]['height'] * lane['yo'] - 16 ) ) + ' c 0,3 2,5 5,5',
- 'style': 'stroke:#0041c4;stroke-width:1;fill:none'
- }
- ]
- root.append(group)
-
- name = groups[i]['name']
- x = str( int( groups[i]['x'] - 10 ) )
- y = str( int( lane['yo'] * (groups[i]['y'] + (float(groups[i]['height']) / 2)) + lane['yh0'] + lane['yh1'] ) )
- label = [
- ['g',
- {'transform': 'translate(' + x + ',' + y + ')'},
- ['g', {'transform': 'rotate(270)'},
- 'text',
- {
- 'text-anchor': 'middle',
- 'class': 'info',
- 'xml:space' : 'preserve'
- },
- ['tspan',name]
- ]
- ]
- ]
- root.append(label)
-
-def renderGaps (root, source, index) :
-
- Stack = []
- svgns = 'http://www.w3.org/2000/svg',
- xlinkns = 'http://www.w3.org/1999/xlink'
-
- if source:
-
- gg = [
- 'g',
- { 'id': 'wavegaps_' + str(index) }
- ]
-
- for i in range( len( source )):
- lane['period'] = source[i].get('period',1)
- lane['phase'] = int( source[i].get('phase',0 ) * 2 )
-
- g = [
- 'g',
- {
- 'id': 'wavegap_' + str(i) + '_' + str(index),
- 'transform': 'translate(0,' + str(lane['y0'] + i * lane['yo']) + ')'
- }
- ]
- gg.append(g)
-
- if source[i].get('wave'):
- text = source[i]['wave']
- Stack = text
- pos = 0
- while len( Stack ) :
- c = Stack [0]
- Stack = Stack[1:]
- if c == '|' :
- b = [
- 'use',
- {
- 'xmlns:xlink':xlinkns,
- 'xlink:href':'#gap',
- 'transform': 'translate(' + str(int(float(lane['xs']) * ((2 * pos + 1) * float(lane['period']) * float(lane['hscale']) - float(lane['phase'])))) + ')'
- }
- ]
- g.append(b)
- pos += 1
-
- root.append( gg )
-
-def is_type_str( var ) :
- if sys.version_info[0] < 3:
- return type( var ) is str or type( var ) is unicode
- else:
- return type( var ) is str
-
-def convert_to_svg( root ) :
-
- svg_output = ''
-
- if type( root ) is list:
- if len(root) >= 2 and type( root[1] ) is dict:
- if len( root ) == 2 :
- svg_output += '<' + root[0] + convert_to_svg( root[1] ) + '/>\n'
- elif len( root ) >= 3 :
- svg_output += '<' + root[0] + convert_to_svg( root[1] ) + '>\n'
- if len( root ) == 3:
- svg_output += convert_to_svg( root[2] )
- else:
- svg_output += convert_to_svg( root[2:] )
- svg_output += '' + root[0] + '>\n'
- elif type( root[0] ) is list:
- for eleml in root:
- svg_output += convert_to_svg( eleml )
- else:
- svg_output += '<' + root[0] + '>\n'
- for eleml in root[1:]:
- svg_output += convert_to_svg( eleml )
- svg_output += '' + root[0] + '>\n'
- elif type( root ) is dict:
- for elemd in root :
- svg_output += ' ' + elemd + '="' + str(root[elemd]) + '"'
- else:
- svg_output += root
-
- return svg_output
-
-if __name__ == '__main__':
-
- if len( sys.argv ) != 5:
- print ( 'Usage : ' + sys.argv[0] + ' source svg ' )
- exit(1)
-
- if sys.argv[3] != 'svg' :
- print ( 'Error: only SVG format supported.' )
- exit(1)
-
- output=[]
- inputfile = sys.argv[2]
- outputfile = sys.argv[4]
-
- with open(inputfile,'r') as f:
- jinput = json.load(f)
-
- renderWaveForm(0,jinput,output)
- svg_output = convert_to_svg(output)
-
- with open(outputfile,'w') as f:
- f.write( svg_output )
From c22025839c5b10b022030574a006e667c54931cd Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Thu, 31 Jan 2019 08:28:51 -0800
Subject: [PATCH 05/35] datasheet now indicates if analytical or characterizer
is used
---
compiler/characterizer/lib.py | 5 +++--
compiler/datasheet/datasheet.py | 6 ++++++
compiler/datasheet/datasheet_gen.py | 5 +++++
3 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py
index a38ecd93..a63c773c 100644
--- a/compiler/characterizer/lib.py
+++ b/compiler/characterizer/lib.py
@@ -526,7 +526,7 @@ class lib:
datasheet = open(OPTS.openram_temp +'/datasheet.info', 'a+')
current_time = datetime.date.today()
- datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},".format(
+ datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},".format(
OPTS.output_name,
OPTS.num_words,
OPTS.num_banks,
@@ -542,7 +542,8 @@ class lib:
lib_name,
OPTS.word_size,
git_id,
- current_time
+ current_time,
+ OPTS.analytical_delay
))
# information of checks
diff --git a/compiler/datasheet/datasheet.py b/compiler/datasheet/datasheet.py
index d15733d5..71d1f9dd 100644
--- a/compiler/datasheet/datasheet.py
+++ b/compiler/datasheet/datasheet.py
@@ -59,6 +59,12 @@ class datasheet():
self.html += self.operating_table.to_html()
self.html += 'Timing and Current Data
'
+ model = ''
+ if self.ANALYTICAL_MODEL:
+ model = "analytical model: results may not be percise"
+ else:
+ model = "spice characterizer"
+ self.html += 'Using '+model+'
'
# self.html += timing_and_current_data(self.timing,table_id='data').__html__()
self.html += self.timing_table.to_html()
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index 24bc3953..664f88c5 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -115,6 +115,10 @@ def parse_characterizer_csv(f, pages):
AREA = row[col]
col += 1
+
+ ANALYTICAL_MODEL = row[col]
+ col += 1
+
for sheet in pages:
if sheet.name == NAME:
@@ -365,6 +369,7 @@ def parse_characterizer_csv(f, pages):
new_sheet.time = DATETIME
new_sheet.DRC = DRC
new_sheet.LVS = LVS
+ new_sheet.ANALYTICAL_MODEL = ANALYTICAL_MODEL
new_sheet.description = [NAME, NUM_WORDS, NUM_BANKS, NUM_RW_PORTS, NUM_W_PORTS,
NUM_R_PORTS, TECH_NAME, MIN_PERIOD, WORD_SIZE, ORIGIN_ID, DATETIME]
From e131af2cc3bca7a8a3e957fd516379874ab0cdd2 Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Wed, 6 Feb 2019 20:31:22 -0800
Subject: [PATCH 06/35] power added to datasheet (finally)
---
compiler/characterizer/lib.py | 40 +++++-
compiler/datasheet/datasheet.py | 5 +-
compiler/datasheet/datasheet_gen.py | 131 +++++++++++++-----
.../example_config_1rw_1r_scn4m_subm.py | 2 +-
4 files changed, 136 insertions(+), 42 deletions(-)
diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py
index a63c773c..76d8e3d8 100644
--- a/compiler/characterizer/lib.py
+++ b/compiler/characterizer/lib.py
@@ -507,7 +507,8 @@ class lib:
def parse_info(self,corner,lib_name):
""" Copies important characterization data to datasheet.info to be added to datasheet """
if OPTS.is_unit_test:
- git_id = 'AAAAAAAAAAAAAAAAAAAA'
+ git_id = 'FFFFFFFFFFFFFFFFFFFF'
+
else:
with open(os.devnull, 'wb') as devnull:
proc = subprocess.Popen(['git','rev-parse','HEAD'], cwd=os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/', stdout=subprocess.PIPE)
@@ -524,7 +525,7 @@ class lib:
git_id = 'Failed to retruieve'
datasheet = open(OPTS.openram_temp +'/datasheet.info', 'a+')
-
+
current_time = datetime.date.today()
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},".format(
OPTS.output_name,
@@ -654,8 +655,41 @@ class lib:
))
+ for port in self.all_ports:
+ name = ''
+ read_write = ''
+ if port in self.read_ports:
+ web_name = " & !WEb{0}".format(port)
+ name = "!CSb{0} & clk{0}{1}".format(port, web_name)
+ read_write = 'Read'
+ datasheet.write("{0},{1},{2},{3},".format(
+ "power",
+ name,
+ read_write,
+
+ np.mean(self.char_port_results[port]["read1_power"] + self.char_port_results[port]["read0_power"])/2
+ ))
+
+ if port in self.write_ports:
+ web_name = " & WEb{0}".format(port)
+ name = "!CSb{0} & !clk{0}{1}".format(port, web_name)
+ read_write = 'Write'
+
+ datasheet.write("{0},{1},{2},{3},".format(
+ 'power',
+ name,
+ read_write,
+ np.mean(self.char_port_results[port]["write1_power"] + self.char_port_results[port]["write0_power"])/2
+
+ ))
+
+ control_str = 'CSb0'
+ for i in range(1, self.total_port_num):
+ control_str += ' & CSb{0}'.format(i)
+
+ datasheet.write("{0},{1},{2},".format('leak', control_str, self.char_sram_results["leakage_power"]))
+
datasheet.write("END\n")
datasheet.close()
-
diff --git a/compiler/datasheet/datasheet.py b/compiler/datasheet/datasheet.py
index 71d1f9dd..84a6eafc 100644
--- a/compiler/datasheet/datasheet.py
+++ b/compiler/datasheet/datasheet.py
@@ -58,7 +58,7 @@ class datasheet():
# self.html += operating_conditions(self.operating,table_id='data').__html__()
self.html += self.operating_table.to_html()
- self.html += 'Timing and Current Data
'
+ self.html += 'Timing Data
'
model = ''
if self.ANALYTICAL_MODEL:
model = "analytical model: results may not be percise"
@@ -68,6 +68,9 @@ class datasheet():
# self.html += timing_and_current_data(self.timing,table_id='data').__html__()
self.html += self.timing_table.to_html()
+ self.html += 'Power Data
'
+ self.html += self.power_table.to_html()
+
self.html += 'Characterization Corners
'
# self.html += characterization_corners(self.corners,table_id='data').__html__()
self.html += self.corners_table.to_html()
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index 664f88c5..51c636c8 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -166,31 +166,31 @@ def parse_characterizer_csv(f, pages):
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('setup falling'):
+ if item[0].endswith('setup falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold rising'):
+ if item[0].endswith('hold rising'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold falling'):
+ if item[0].endswith('hold falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
col += 1
@@ -205,31 +205,31 @@ def parse_characterizer_csv(f, pages):
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('cell fall'):
+ if item[0].endswith('cell fall'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('rise transition'):
+ if item[0].endswith('rise transition'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('fall transition'):
+ if item[0].endswith('fall transition'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
col += 1
@@ -244,31 +244,31 @@ def parse_characterizer_csv(f, pages):
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('setup falling'):
+ if item[0].endswith('setup falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold rising'):
+ if item[0].endswith('hold rising'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold falling'):
+ if item[0].endswith('hold falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
col += 1
@@ -283,31 +283,31 @@ def parse_characterizer_csv(f, pages):
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('setup falling'):
+ if item[0].endswith('setup falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold rising'):
+ if item[0].endswith('hold rising'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold falling'):
+ if item[0].endswith('hold falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
col += 1
@@ -322,31 +322,31 @@ def parse_characterizer_csv(f, pages):
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('setup falling'):
+ if item[0].endswith('setup falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold rising'):
+ if item[0].endswith('hold rising'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold falling'):
+ if item[0].endswith('hold falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
col += 1
@@ -354,8 +354,35 @@ def parse_characterizer_csv(f, pages):
for element in row[col_start: col - 1]:
sheet.description.append(str(element))
break
+ while(True):
+ col_start = col
+ if row[col] == 'power':
+ for item in sheet.power_table.rows:
+ if item[0].startswith(row[col+1]):
+ if item[2].startswith('{0} Rising'.format(row[col+2])):
+ if float(item[2]) < float(row[col+3]):
+ item[2] = row[col+3]
+ if item[2].startswith('{0} Falling'.format(row[col+2])):
+ if float(item[2]) < float(row[col+3]):
+ item[2] = row[col+3]
+ col += 4
+ else:
+ break
- new_sheet.corners_table.add_row([PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
+ while(True):
+ col_start = col
+ if row[col] == 'leak':
+ for item in sheet.power_table.rows:
+ if item[0].startswith(row[col+1]):
+ if float(item[2]) < float(row[col+2]):
+ item[2] = row[col+2]
+ col += 3
+
+ else:
+ break
+
+ new_sheet.corners_table.add_row(
+ [PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
new_sheet.dlv_table.add_row(
['.lib', 'Synthesis models', '{1}'.format(LIB_NAME, LIB_NAME.replace(OUT_DIR, ''))])
@@ -376,7 +403,8 @@ def parse_characterizer_csv(f, pages):
new_sheet.corners_table = table_gen.table_gen("corners")
new_sheet.corners_table.add_row(
['Transistor Type', 'Power Supply', 'Temperature', 'Corner Name'])
- new_sheet.corners_table.add_row([PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
+ new_sheet.corners_table.add_row(
+ [PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
new_sheet.operating_table = table_gen.table_gen(
"operating_table")
new_sheet.operating_table.add_row(
@@ -393,6 +421,9 @@ def parse_characterizer_csv(f, pages):
# failed to provide non-zero MIN_PERIOD
new_sheet.operating_table.add_row(
['Operating Frequency (F)', '', '', "not available in netlist only", 'MHz'])
+ new_sheet.power_table = table_gen.table_gen("power")
+ new_sheet.power_table.add_row(
+ ['Pins', 'Mode', 'Power', 'Units'])
new_sheet.timing_table = table_gen.table_gen("timing")
new_sheet.timing_table.add_row(
['Parameter', 'Min', 'Max', 'Units'])
@@ -523,6 +554,32 @@ def parse_characterizer_csv(f, pages):
sheet.description.append(str(element))
break
+ while(True):
+ start = col
+ if(row[col].startswith('power')):
+ new_sheet.power_table.add_row([row[col+1],
+ '{0} Rising'.format(
+ row[col+2]),
+ row[col+3][0:6],
+ 'mW']
+ )
+ new_sheet.power_table.add_row([row[col+1],
+ '{0} Falling'.format(
+ row[col+2]),
+ row[col+3][0:6],
+ 'mW']
+ )
+
+ col += 4
+
+ elif(row[col].startswith('leak')):
+ new_sheet.power_table.add_row(
+ [row[col+1], 'leakage', row[col+2], 'mW'])
+ col += 3
+
+ else:
+ break
+
new_sheet.dlv_table = table_gen.table_gen("dlv")
new_sheet.dlv_table.add_row(['Type', 'Description', 'Link'])
diff --git a/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py b/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py
index 4d09cfee..87c1304d 100644
--- a/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py
+++ b/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py
@@ -10,7 +10,7 @@ num_w_ports = 0
tech_name = "scn4m_subm"
process_corners = ["TT"]
supply_voltages = [5.0]
-temperatures = [25]
+temperatures = [25,50]
output_path = "temp"
output_name = "sram_1rw_1r_{0}_{1}_{2}".format(word_size,num_words,tech_name)
From 6cde6beafa86f53ee64df4568679bb46f5d4924e Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Thu, 7 Feb 2019 06:33:39 -0800
Subject: [PATCH 07/35] added documetation to functions
---
compiler/characterizer/lib.py | 10 ++++++++--
compiler/datasheet/datasheet.py | 21 ++++++++++-----------
compiler/datasheet/datasheet_gen.py | 14 ++++++++------
compiler/datasheet/table_gen.py | 17 +++++++++++------
4 files changed, 37 insertions(+), 25 deletions(-)
diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py
index 76d8e3d8..60452d76 100644
--- a/compiler/characterizer/lib.py
+++ b/compiler/characterizer/lib.py
@@ -511,6 +511,7 @@ class lib:
else:
with open(os.devnull, 'wb') as devnull:
+ # parses the mose recent git commit id - requres git is installed
proc = subprocess.Popen(['git','rev-parse','HEAD'], cwd=os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/', stdout=subprocess.PIPE)
git_id = str(proc.stdout.read())
@@ -519,7 +520,7 @@ class lib:
git_id = git_id[2:-3]
except:
pass
-
+ # check if git id is valid
if len(git_id) != 40:
debug.warning("Failed to retrieve git id")
git_id = 'Failed to retruieve'
@@ -527,6 +528,7 @@ class lib:
datasheet = open(OPTS.openram_temp +'/datasheet.info', 'a+')
current_time = datetime.date.today()
+ # write static information to be parser later
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},".format(
OPTS.output_name,
OPTS.num_words,
@@ -558,6 +560,7 @@ class lib:
datasheet.write("{0},{1},".format(DRC, LVS))
datasheet.write(str(self.sram.width * self.sram.height)+',')
+ # write timing information for all ports
for port in self.all_ports:
#DIN timings
if port in self.write_ports:
@@ -654,10 +657,12 @@ class lib:
))
-
+ # write power information
for port in self.all_ports:
name = ''
read_write = ''
+
+ # write dynamic power usage
if port in self.read_ports:
web_name = " & !WEb{0}".format(port)
name = "!CSb{0} & clk{0}{1}".format(port, web_name)
@@ -684,6 +689,7 @@ class lib:
))
+ # write leakage power
control_str = 'CSb0'
for i in range(1, self.total_port_num):
control_str += ' & CSb{0}'.format(i)
diff --git a/compiler/datasheet/datasheet.py b/compiler/datasheet/datasheet.py
index 84a6eafc..4b5cb741 100644
--- a/compiler/datasheet/datasheet.py
+++ b/compiler/datasheet/datasheet.py
@@ -28,11 +28,12 @@ class datasheet():
# for item in self.description:
# self.html += item + ','
self.html += '-->'
-
+ # Add vlsida logo
vlsi_logo = 0
with open(os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/datasheet/assets/vlsi_logo.png', "rb") as image_file:
vlsi_logo = base64.b64encode(image_file.read())
+ # Add openram logo
openram_logo = 0
with open(os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/datasheet/assets/openram_logo_placeholder.png', "rb") as image_file:
openram_logo = base64.b64encode(image_file.read())
@@ -49,32 +50,30 @@ class datasheet():
'LVS errors: ' + str(self.LVS) + '
'
self.html += '' + \
'Git commit id: ' + str(self.git_id) + '
'
-
+ # print port table
self.html += 'Ports and Configuration
'
-# self.html += in_out(self.io,table_id='data').__html__().replace('<','<').replace('"','"').replace('>',">")
self.html += self.io_table.to_html()
-
+
+ # print operating condidition information
self.html += 'Operating Conditions
'
-# self.html += operating_conditions(self.operating,table_id='data').__html__()
self.html += self.operating_table.to_html()
+ # check if analytical model is being used
self.html += 'Timing Data
'
model = ''
if self.ANALYTICAL_MODEL:
model = "analytical model: results may not be percise"
else:
model = "spice characterizer"
+ # display timing data
self.html += 'Using '+model+'
'
-# self.html += timing_and_current_data(self.timing,table_id='data').__html__()
self.html += self.timing_table.to_html()
-
+ # display power data
self.html += 'Power Data
'
self.html += self.power_table.to_html()
-
+ # display corner information
self.html += 'Characterization Corners
'
-# self.html += characterization_corners(self.corners,table_id='data').__html__()
self.html += self.corners_table.to_html()
-
+ # display deliverables table
self.html += 'Deliverables
'
-# self.html += deliverables(self.dlv,table_id='data').__html__().replace('<','<').replace('"','"').replace('>',">")
self.html += self.dlv_table.to_html()
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index 51c636c8..44db3f9b 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -4,7 +4,6 @@ This is a script to load data from the characterization and layout processes int
a web friendly html datasheet.
"""
# TODO:
-# include power
# Diagram generation
# Improve css
@@ -152,7 +151,7 @@ def parse_characterizer_csv(f, pages):
1000/float(MIN_PERIOD)))
except Exception:
pass
-
+ # check current .lib file produces the slowest timing results
while(True):
col_start = col
if(row[col].startswith('DIN')):
@@ -354,6 +353,8 @@ def parse_characterizer_csv(f, pages):
for element in row[col_start: col - 1]:
sheet.description.append(str(element))
break
+
+ #check if new power is worse the previous
while(True):
col_start = col
if row[col] == 'power':
@@ -368,7 +369,7 @@ def parse_characterizer_csv(f, pages):
col += 4
else:
break
-
+ # check if new leakge is worse the previous
while(True):
col_start = col
if row[col] == 'leak':
@@ -380,7 +381,7 @@ def parse_characterizer_csv(f, pages):
else:
break
-
+ # add new corner information
new_sheet.corners_table.add_row(
[PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
new_sheet.dlv_table.add_row(
@@ -427,6 +428,7 @@ def parse_characterizer_csv(f, pages):
new_sheet.timing_table = table_gen.table_gen("timing")
new_sheet.timing_table.add_row(
['Parameter', 'Min', 'Max', 'Units'])
+ # parse initial timing information
while(True):
col_start = col
if(row[col].startswith('DIN')):
@@ -553,7 +555,7 @@ def parse_characterizer_csv(f, pages):
for element in row[col_start:col-1]:
sheet.description.append(str(element))
break
-
+ # parse initial power and leakage information
while(True):
start = col
if(row[col].startswith('power')):
@@ -618,7 +620,7 @@ def parse_characterizer_csv(f, pages):
class datasheet_gen():
def datasheet_write(name):
-
+ """writes the datasheet to a file"""
in_dir = OPTS.openram_temp
if not (os.path.isdir(in_dir)):
diff --git a/compiler/datasheet/table_gen.py b/compiler/datasheet/table_gen.py
index 18590739..8f94e896 100644
--- a/compiler/datasheet/table_gen.py
+++ b/compiler/datasheet/table_gen.py
@@ -1,24 +1,29 @@
class table_gen:
- def __init__(self,name):
+ """small library of functions to generate the html tables"""
+
+ def __init__(self, name):
self.name = name
self.rows = []
self.table_id = 'data'
- def add_row(self,row):
+ def add_row(self, row):
+ """add a row to table_gen object"""
self.rows.append(row)
def gen_table_head(self):
+ """generate html table header"""
html = ''
html += ''
html += ''
for col in self.rows[0]:
- html += '| ' + str(col) + ' | '
+ html += '' + str(col) + ' | '
html += '
'
html += ''
return html
def gen_table_body(self):
+ """generate html body (used after gen_table_head)"""
html = ''
html += ''
@@ -31,13 +36,13 @@ class table_gen:
html += ''
html += ''
return html
-
+
def to_html(self):
-
+ """writes table_gen object to inline html"""
html = ''
html += ''
html += self.gen_table_head()
html += self.gen_table_body()
html += '
'
-
+
return html
From bfc20a9aa9958308adc431bc399046639a440164 Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Thu, 7 Feb 2019 06:38:07 -0800
Subject: [PATCH 08/35] removes debug corners
---
compiler/example_configs/example_config_1rw_1r_scn4m_subm.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py b/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py
index 87c1304d..4d09cfee 100644
--- a/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py
+++ b/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py
@@ -10,7 +10,7 @@ num_w_ports = 0
tech_name = "scn4m_subm"
process_corners = ["TT"]
supply_voltages = [5.0]
-temperatures = [25,50]
+temperatures = [25]
output_path = "temp"
output_name = "sram_1rw_1r_{0}_{1}_{2}".format(word_size,num_words,tech_name)
From e890c0e188ee8cf03bdf887d40289bd236207251 Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Wed, 13 Feb 2019 15:21:16 -0800
Subject: [PATCH 09/35] fixed -v logging
---
compiler/debug.py | 36 +++++++++++++++++++++++-------------
1 file changed, 23 insertions(+), 13 deletions(-)
diff --git a/compiler/debug.py b/compiler/debug.py
index e6c6a1bd..f25ff4b5 100644
--- a/compiler/debug.py
+++ b/compiler/debug.py
@@ -16,7 +16,7 @@ def check(check, str):
index) = inspect.getouterframes(inspect.currentframe())[1]
sys.stderr.write("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
- log("ERROR: file {0}: line {1}: {2}\n".format(
+ logger.log("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
assert 0
@@ -27,7 +27,7 @@ def error(str, return_value=0):
index) = inspect.getouterframes(inspect.currentframe())[1]
sys.stderr.write("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
- log("ERROR: file {0}: line {1}: {2}\n".format(
+ logger.log("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
assert return_value == 0
@@ -38,7 +38,7 @@ def warning(str):
index) = inspect.getouterframes(inspect.currentframe())[1]
sys.stderr.write("WARNING: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
- log("WARNING: file {0}: line {1}: {2}\n".format(
+ logger.log("WARNING: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
@@ -48,17 +48,27 @@ def print_raw(str):
def log(str):
- if log.create_file:
- compile_log = open(globals.OPTS.output_path +
- globals.OPTS.output_name + '.log', "w")
- log.create_file = 0
+ if globals.OPTS.output_name != '':
+ if log.create_file:
+ compile_log = open(globals.OPTS.output_path +
+ globals.OPTS.output_name + '.log', "w+")
+ log.create_file = 0
+ else:
+ compile_log = open(globals.OPTS.output_path +
+ globals.OPTS.output_name + '.log', "a")
+
+ if len(log.setup_output) != 0:
+ for line in log.setup_output:
+ compile_log.write(line)
+ log.setup_output = []
+ compile_log.write(str + '\n')
else:
- compile_log = open(globals.OPTS.output_path +
- globals.OPTS.output_name + '.log', "a")
- compile_log.write(str + '\n')
-log.create_file = 1
+ log.setup_output.append(str + "\n")
+# use a static list of strings to store messages until the global paths are set up
+log.setup_output = []
+log.create_file = 1
def info(lev, str):
@@ -71,5 +81,5 @@ def info(lev, str):
class_name = ""
else:
class_name = mod.__name__
- print_raw("[{0}/{1}]: {2}".format(class_name, frm[0].f_code.co_name, str))
-
+ print_raw("[{0}/{1}]: {2}".format(class_name,
+ frm[0].f_code.co_name, str))
From c359bbf42ab42907134853214da020a3a7ff7433 Mon Sep 17 00:00:00 2001
From: Matt Guthaus
Date: Wed, 13 Feb 2019 17:01:26 -0800
Subject: [PATCH 10/35] Fix deprecation warnings in regex by converting to raw
strings. Add error if unable to find DRC errors in Magic.
---
compiler/verify/calibre.py | 10 +++++-----
compiler/verify/magic.py | 4 ++++
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/compiler/verify/calibre.py b/compiler/verify/calibre.py
index a1bbbc3e..dde7ea5a 100644
--- a/compiler/verify/calibre.py
+++ b/compiler/verify/calibre.py
@@ -126,9 +126,9 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False):
f.close()
# those lines should be the last 3
results = results[-3:]
- geometries = int(re.split("\W+", results[0])[5])
- rulechecks = int(re.split("\W+", results[1])[4])
- errors = int(re.split("\W+", results[2])[5])
+ geometries = int(re.split(r'\W+', results[0])[5])
+ rulechecks = int(re.split(r'\W+', results[1])[4])
+n errors = int(re.split(r'\W+', results[2])[5])
# always display this summary
if errors > 0:
@@ -227,7 +227,7 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False):
incorrect = list(filter(test.search, results))
# Errors begin with "Error:"
- test = re.compile("\s+Error:")
+ test = re.compile(r'\s+Error:')
errors = list(filter(test.search, results))
for e in errors:
debug.error(e.strip("\n"))
@@ -363,7 +363,7 @@ def correct_port(name, output_file_name, ref_file_name):
pex_file.seek(match_index_start)
rest_text = pex_file.read()
# locate the end of circuit definition line
- match = re.search("\* \n", rest_text)
+ match = re.search(r'\* \n', rest_text)
match_index_end = match.start()
# store the unchanged part of pex file in memory
pex_file.seek(0)
diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py
index 490a09a1..0af8e5ef 100644
--- a/compiler/verify/magic.py
+++ b/compiler/verify/magic.py
@@ -140,11 +140,15 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False):
debug.error("Unable to retrieve DRC results file. Is magic set up?",1)
results = f.readlines()
f.close()
+ errors=1
# those lines should be the last 3
for line in results:
if "Total DRC errors found:" in line:
errors = int(re.split(": ", line)[1])
break
+ else:
+ debug.error("Unable to find the total error line in Magic output.",1)
+
# always display this summary
if errors > 0:
From d4c21cd26e848b7cdf503edbdcbbd4639d42237f Mon Sep 17 00:00:00 2001
From: Matt Guthaus
Date: Wed, 13 Feb 2019 17:41:33 -0800
Subject: [PATCH 11/35] Remove extraneous character.
---
compiler/verify/calibre.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/compiler/verify/calibre.py b/compiler/verify/calibre.py
index dde7ea5a..2de76386 100644
--- a/compiler/verify/calibre.py
+++ b/compiler/verify/calibre.py
@@ -128,7 +128,7 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False):
results = results[-3:]
geometries = int(re.split(r'\W+', results[0])[5])
rulechecks = int(re.split(r'\W+', results[1])[4])
-n errors = int(re.split(r'\W+', results[2])[5])
+ errors = int(re.split(r'\W+', results[2])[5])
# always display this summary
if errors > 0:
From e3ff9b53e9bf21b190faa6171807c13b9861f69e Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Thu, 14 Feb 2019 07:01:35 -0800
Subject: [PATCH 12/35] fixed area not being found
---
compiler/characterizer/lib.py | 1 +
compiler/datasheet/datasheet_gen.py | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py
index 60452d76..3b4f465b 100644
--- a/compiler/characterizer/lib.py
+++ b/compiler/characterizer/lib.py
@@ -559,6 +559,7 @@ class lib:
LVS = str(total_lvs_errors)
datasheet.write("{0},{1},".format(DRC, LVS))
+ # write area
datasheet.write(str(self.sram.width * self.sram.height)+',')
# write timing information for all ports
for port in self.all_ports:
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index 44db3f9b..1644df5f 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -112,10 +112,10 @@ def parse_characterizer_csv(f, pages):
LVS = row[col]
col += 1
- AREA = row[col]
+ ANALYTICAL_MODEL = row[col]
col += 1
- ANALYTICAL_MODEL = row[col]
+ AREA = row[col]
col += 1
for sheet in pages:
From d533a8ae2684068edd9c55a9806087600d430151 Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Fri, 15 Feb 2019 21:45:05 -0800
Subject: [PATCH 13/35] fixed logger typo
---
compiler/debug.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/compiler/debug.py b/compiler/debug.py
index f25ff4b5..b7819bcc 100644
--- a/compiler/debug.py
+++ b/compiler/debug.py
@@ -16,7 +16,7 @@ def check(check, str):
index) = inspect.getouterframes(inspect.currentframe())[1]
sys.stderr.write("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
- logger.log("ERROR: file {0}: line {1}: {2}\n".format(
+ log("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
assert 0
@@ -27,7 +27,7 @@ def error(str, return_value=0):
index) = inspect.getouterframes(inspect.currentframe())[1]
sys.stderr.write("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
- logger.log("ERROR: file {0}: line {1}: {2}\n".format(
+ log("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
assert return_value == 0
@@ -38,7 +38,7 @@ def warning(str):
index) = inspect.getouterframes(inspect.currentframe())[1]
sys.stderr.write("WARNING: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
- logger.log("WARNING: file {0}: line {1}: {2}\n".format(
+ log("WARNING: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
@@ -48,7 +48,7 @@ def print_raw(str):
def log(str):
- if globals.OPTS.output_name != '':
+ if globals.OPTS.output_name != '':
if log.create_file:
compile_log = open(globals.OPTS.output_path +
globals.OPTS.output_name + '.log', "w+")
From 39bcdc8a584b4fad1eb41c9cbf617c0fe5af34e4 Mon Sep 17 00:00:00 2001
From: Matt Guthaus
Date: Sun, 17 Feb 2019 10:35:56 -0800
Subject: [PATCH 14/35] Remove outdated docs. Add Docker info to README.md
---
README.md | 13 +
docs/Makefile | 6 -
docs/control.tex | 4 -
docs/debug.tex | 49 -
docs/figs/2t1_single_level_column_mux.pdf | Bin 33644 -> 0 bytes
docs/figs/2t4decoder.pdf | Bin 43903 -> 0 bytes
docs/figs/4t16decoder.pdf | Bin 134359 -> 0 bytes
docs/figs/4t1_single_level_column_mux.pdf | Bin 52409 -> 0 bytes
docs/figs/Col_mux.svg | 1401 ------
docs/figs/Logic Diagram.svg | 747 ---
docs/figs/ZBT.pdf | Bin 41365 -> 0 bytes
docs/figs/bank.pdf | Bin 76490 -> 0 bytes
docs/figs/bank2.pdf | Bin 34726 -> 0 bytes
docs/figs/bank4.pdf | Bin 47186 -> 0 bytes
docs/figs/cell_6t_schem.pdf | Bin 16855 -> 0 bytes
docs/figs/cell_6t_schem.svg | 522 ---
docs/figs/class_hierarchy.dot | 10 -
docs/figs/class_hierarchy.pdf | Bin 15244 -> 0 bytes
docs/figs/class_hierarchy.sh | 2 -
docs/figs/column_tree_mux.pdf | Bin 11716 -> 0 bytes
docs/figs/column_tree_mux.svg | 665 ---
docs/figs/control_logic.pdf | Bin 90757 -> 0 bytes
docs/figs/gds_file.pdf | Bin 13795 -> 0 bytes
docs/figs/layout_view_1024_16_annotated.eps | 4512 -------------------
docs/figs/layout_view_1024_16_annotated.pdf | Bin 98968 -> 0 bytes
docs/figs/methodology.eps | 866 ----
docs/figs/methodology.pdf | Bin 14709 -> 0 bytes
docs/figs/methodology.svg | 1053 -----
docs/figs/ms_flop_schem.pdf | Bin 39479 -> 0 bytes
docs/figs/ms_flop_schem.svg | 1562 -------
docs/figs/nand_decoder_schem.pdf | Bin 49650 -> 0 bytes
docs/figs/nand_decoder_schem.svg | 909 ----
docs/figs/nor2.pdf | Bin 34601 -> 0 bytes
docs/figs/overall_flow.pdf | Bin 35879 -> 0 bytes
docs/figs/overview.dia | Bin 5079 -> 0 bytes
docs/figs/overview.png | Bin 43911 -> 0 bytes
docs/figs/pinv.pdf | Bin 103664 -> 0 bytes
docs/figs/precharge_schem.pdf | Bin 6765 -> 0 bytes
docs/figs/precharge_schem.svg | 311 --
docs/figs/ptx.pdf | Bin 75391 -> 0 bytes
docs/figs/ptx.svg | 1557 -------
docs/figs/replica_bitline.pdf | Bin 60217 -> 0 bytes
docs/figs/replica_cell.pdf | Bin 20121 -> 0 bytes
docs/figs/sense_amp_schem.pdf | Bin 14383 -> 0 bytes
docs/figs/sense_amp_schem.svg | 113 -
docs/figs/sram_architecture.svg | 679 ---
docs/figs/sram_overview.eps | 3128 -------------
docs/figs/sram_overview.pdf | Bin 21232 -> 0 bytes
docs/figs/sram_overview.svg | 760 ----
docs/figs/timing_read.pdf | Bin 9066 -> 0 bytes
docs/figs/timing_read.svg | 633 ---
docs/figs/timing_write.pdf | Bin 13163 -> 0 bytes
docs/figs/timing_write.svg | 811 ----
docs/figs/tree_column_mux_schem.pdf | Bin 48611 -> 0 bytes
docs/figs/wordline_driver.pdf | Bin 45907 -> 0 bytes
docs/figs/write_driver_schem.pdf | Bin 7324 -> 0 bytes
docs/figs/write_driver_schem.svg | 643 ---
docs/figs/xsram_block.svg | 199 -
docs/gdsmill.tex | 33 -
docs/implementation.tex | 389 --
docs/intro.tex | 272 --
docs/modules.tex | 601 ---
docs/openram_manual.pdf | Bin 1260616 -> 0 bytes
docs/openram_manual.tex | 108 -
docs/overview.tex | 90 -
docs/parameterized.tex | 258 --
docs/porting.tex | 47 -
docs/timing.tex | 230 -
docs/unittests.tex | 100 -
69 files changed, 13 insertions(+), 23270 deletions(-)
delete mode 100644 docs/Makefile
delete mode 100644 docs/control.tex
delete mode 100644 docs/debug.tex
delete mode 100644 docs/figs/2t1_single_level_column_mux.pdf
delete mode 100644 docs/figs/2t4decoder.pdf
delete mode 100644 docs/figs/4t16decoder.pdf
delete mode 100644 docs/figs/4t1_single_level_column_mux.pdf
delete mode 100644 docs/figs/Col_mux.svg
delete mode 100644 docs/figs/Logic Diagram.svg
delete mode 100644 docs/figs/ZBT.pdf
delete mode 100644 docs/figs/bank.pdf
delete mode 100644 docs/figs/bank2.pdf
delete mode 100644 docs/figs/bank4.pdf
delete mode 100644 docs/figs/cell_6t_schem.pdf
delete mode 100644 docs/figs/cell_6t_schem.svg
delete mode 100644 docs/figs/class_hierarchy.dot
delete mode 100644 docs/figs/class_hierarchy.pdf
delete mode 100644 docs/figs/class_hierarchy.sh
delete mode 100644 docs/figs/column_tree_mux.pdf
delete mode 100644 docs/figs/column_tree_mux.svg
delete mode 100644 docs/figs/control_logic.pdf
delete mode 100644 docs/figs/gds_file.pdf
delete mode 100644 docs/figs/layout_view_1024_16_annotated.eps
delete mode 100644 docs/figs/layout_view_1024_16_annotated.pdf
delete mode 100644 docs/figs/methodology.eps
delete mode 100644 docs/figs/methodology.pdf
delete mode 100644 docs/figs/methodology.svg
delete mode 100644 docs/figs/ms_flop_schem.pdf
delete mode 100644 docs/figs/ms_flop_schem.svg
delete mode 100644 docs/figs/nand_decoder_schem.pdf
delete mode 100644 docs/figs/nand_decoder_schem.svg
delete mode 100644 docs/figs/nor2.pdf
delete mode 100644 docs/figs/overall_flow.pdf
delete mode 100644 docs/figs/overview.dia
delete mode 100644 docs/figs/overview.png
delete mode 100644 docs/figs/pinv.pdf
delete mode 100644 docs/figs/precharge_schem.pdf
delete mode 100644 docs/figs/precharge_schem.svg
delete mode 100644 docs/figs/ptx.pdf
delete mode 100644 docs/figs/ptx.svg
delete mode 100644 docs/figs/replica_bitline.pdf
delete mode 100644 docs/figs/replica_cell.pdf
delete mode 100644 docs/figs/sense_amp_schem.pdf
delete mode 100644 docs/figs/sense_amp_schem.svg
delete mode 100644 docs/figs/sram_architecture.svg
delete mode 100644 docs/figs/sram_overview.eps
delete mode 100644 docs/figs/sram_overview.pdf
delete mode 100644 docs/figs/sram_overview.svg
delete mode 100644 docs/figs/timing_read.pdf
delete mode 100644 docs/figs/timing_read.svg
delete mode 100644 docs/figs/timing_write.pdf
delete mode 100644 docs/figs/timing_write.svg
delete mode 100644 docs/figs/tree_column_mux_schem.pdf
delete mode 100644 docs/figs/wordline_driver.pdf
delete mode 100644 docs/figs/write_driver_schem.pdf
delete mode 100644 docs/figs/write_driver_schem.svg
delete mode 100644 docs/figs/xsram_block.svg
delete mode 100644 docs/gdsmill.tex
delete mode 100644 docs/implementation.tex
delete mode 100644 docs/intro.tex
delete mode 100644 docs/modules.tex
delete mode 100644 docs/openram_manual.pdf
delete mode 100644 docs/openram_manual.tex
delete mode 100644 docs/overview.tex
delete mode 100644 docs/parameterized.tex
delete mode 100644 docs/porting.tex
delete mode 100644 docs/timing.tex
delete mode 100644 docs/unittests.tex
diff --git a/README.md b/README.md
index 2141c480..aa5a1705 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,17 @@ predictive and fabricable technologies.
# Basic Setup
+## Docker Image
+
+We have a pre-configured Ubuntu [Docker](https://www.docker.com/) image
+available that has all tools installed for the [SCMOS] process. It is
+available at [docker hub](https://hub.docker.com/r/vlsida/openram-ubuntu).
+Please see
+[our README.md](https://github.com/VLSIDA/openram-docker-images/blob/master/README.md)
+for information on how to use this docker image.
+
+## Dependencies
+
The OpenRAM compiler has very few dependencies:
+ [Ngspice] 26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later)
+ Python 3.5 or higher
@@ -40,6 +51,8 @@ You must set two environment variables:
+ OPENRAM\_HOME should point to the compiler source directory.
+ OPENERAM\_TECH should point to a root technology directory.
+## Environment
+
For example add this to your .bashrc:
```
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index b0d853fa..00000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,6 +0,0 @@
-all: openram_manual.tex
- pdflatex openram_manual
-bib:
- bibtex openram_manual
-clean:
- rm -f openram_manual.pdf *.aux *.bbl *.blq *.dvi *.log *.lot *.toc *.lof *.blg
diff --git a/docs/control.tex b/docs/control.tex
deleted file mode 100644
index 472461d1..00000000
--- a/docs/control.tex
+++ /dev/null
@@ -1,4 +0,0 @@
-\section{Internal Control Signals}
-\label{sec:control}
-
-This section not needed... All information is in Section~\ref{sec:timing} (Timing).
diff --git a/docs/debug.tex b/docs/debug.tex
deleted file mode 100644
index 98bab10d..00000000
--- a/docs/debug.tex
+++ /dev/null
@@ -1,49 +0,0 @@
-\section{Debug Framework}
-\label{sec:debug}
-
-All output in OpenRAM should use the shared debug framework. This is
-still under development but is in a usable state. It is going to be
-replaced with the Python Logging framework which is quite simple.
-
-All of the debug framework is contained in debug.py and is based
-around the concept of a ``debug level'' which is a single global
-variable in this file. This level is, by default, 0 which will output
-normal minimal output. The general guidelines for debug output are:
-\begin{itemize}
-\item 0 Normal output
-\item 1 Verbose output
-\item 2 Detailed output
-\item 3+ Excessively detailed output
-\end{itemize}
-
-The debug level can be adjusted on the command line when arguments are parsed using the ``-v'' flag. Adding more ``-v'' flags will increase the debug level as in the following examples:
-\begin{verbatim}
-python tests/01_library_drc_test.py -vv
-python openram.py 4 16 -v -v
-\end{verbatim}
-which each put the program in debug level 2 (detailed output).
-
-Since every module may output a lot of information in the higher debug
-levels, the output format is standardized to allow easy searching via
-grep or other command-line tools. The standard output formatting is
-used through three interface functions:
-\begin{itemize}
-\item debug.info(int, msg)
-\item debug.warning(msg)
-\item debug.error(msg)
-\end{itemize}
-The msg string in each case can be any string format including data or
-other useful debug information. The string should also contain
-information to make it human understandable. {\bf It should not just be
- a number!} The warning and error messages are independent of debug
-levels while the info message will only print the message if the
-current debug level is above the parameter value.
-
-The output format of the debug info messages are:
-\begin{verbatim}
-[ module ]: msg
-\end{verbatim}
-where module is the calling module name and msg is the string
-provided. This enables a grep command to get the relevant lines. The
-warning and error messages include the file name and line number of
-the warning/error.
diff --git a/docs/figs/2t1_single_level_column_mux.pdf b/docs/figs/2t1_single_level_column_mux.pdf
deleted file mode 100644
index 949173590f1e8bdeeb948a0c7bed8b006328ec94..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 33644
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 ?X
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
HwD5ld(g3<%
diff --git a/docs/figs/2t4decoder.pdf b/docs/figs/2t4decoder.pdf
deleted file mode 100644
index acd551d7789881bac1b6bd4badb23ae39ce6fc6d..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 43903
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 |