/* This file is part of JOP, the Java Optimized Processor see <http://www.jopdesign.com/> Copyright (C) 2004, Ed Anuff (ed@anuff.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ import java.io.*; import java.util.*; /** * Generate a memory module using one or more BlockRams with the appropriate * address and data decoding and pre-initialization. * * Can use either a text file with hex values or Altera-style MIF file for * initialization data. * * @author Ed Anuff (ed@anuff.com) * */ public class BlockGen { static String build_date = "Feb 2 2004 4:01 PM PST"; int depth = 512; int width = 8; int block = 4096; String src_filename = ""; String dst_filename = ""; File src_file; String module_name = ""; String dst_dir = ""; boolean init_ram = false; boolean preserve_depth = false; public BlockGen(String[] args) { processOptions(args); } public void process() { try { if (!stringEmpty(src_filename)) { src_file = new File(src_filename); dst_dir = src_file.getParent(); } if (stringEmpty(module_name) && (src_file.getName().indexOf('.') > 0)) { module_name = src_file.getName().substring(0, src_file.getName().indexOf('.')); } if (stringEmpty(module_name)) module_name = "ram"; if (stringEmpty(dst_filename)) dst_filename = dst_dir + File.separator + module_name + ".vhd"; System.out.println("BlockRam Generator"); System.out.println("Module: " + module_name); String binary_string = ""; if (!stringEmpty(src_filename)) { File file = new File(src_filename); if (!file.exists()) { System.out.println("Input file not found: " + src_filename); System.exit(-1); } init_ram = true; if (src_filename.endsWith(".mif")) binary_string = parseMifFile(); else binary_string = parseTextFile(); } if (init_ram) { System.out.println("Input file: " + src_filename); } else { System.out.println("No input file for pre-initialization specified."); } System.out.println("Output file: " + dst_filename); PrintWriter pw = new PrintWriter(new FileWriter(dst_filename)); int bitcount = width * depth; if (init_ram) bitcount = binary_string.length(); int block_bitcount = width * depth; if (bitcount > block_bitcount) { System.out.println("Data (" + bitcount + " bits) is larger than BlockRam capacity at the specified width and depth."); System.exit(1); } if ((bitcount % width) != 0) { System.out.println("Input data not bit aligned to the specified width."); System.exit(1); } int block_count = 1; int port_width = 1; int max_port_width = 16; boolean ramb16 = block == 16384; if (ramb16) max_port_width = 32; while (port_width <= max_port_width) { if (depth <= (block / port_width)) { block_count = width / port_width; } if (depth * 2 > (block / port_width)) { break; } port_width = port_width * 2; } if (port_width > max_port_width) port_width = max_port_width; if (block_count < 1) block_count = 1; int pl = 0; int ph = port_width - 1; int ah = Integer.toBinaryString((block / port_width) - 1).length() - 1; int wh = width - 1; int pbh = 0; int cpw = port_width; if (port_width == 16) { pbh = 1; } else if (port_width == 32) { pbh = 3; } String pbhi = zero(pbh + 1); if (ramb16) cpw = port_width + pbh + 1; boolean pad_addr = false; int mah = ah; String addr_prefix = ""; if (preserve_depth) { mah = Integer.toBinaryString(depth - 1).length() - 1; if (mah < ah) { pad_addr = true; addr_prefix = "p_"; } } String rtype = "RAMB4"; String rsta = "RSTA"; String rstb = "RSTB"; if (ramb16) { rtype = "RAMB16"; rsta = "SSRA"; rstb = "SSRB"; } pw.println("--"); pw.println("-- " + (new File(dst_filename)).getName()); pw.println("--"); pw.println("-- Generated by BlockGen"); pw.println("-- " + (new Date())); pw.println("--"); if (ramb16) pw.println("-- This module will synthesize on Spartan3 and Virtex2/2Pro/2ProX devices."); else pw.println("-- This module will synthesize on Spartan2/2E and Virtex/E devices."); pw.println("--"); pw.println(); pw.println("library IEEE;"); pw.println("use IEEE.std_logic_1164.all;"); pw.println("use IEEE.std_logic_arith.all;"); pw.println("use IEEE.std_logic_unsigned.all;"); pw.println("library unisim;"); pw.println("use unisim.vcomponents.all;"); pw.println(); pw.println("entity " + module_name + " is"); pw.println("\tport ("); pw.println("\t\ta_rst : in std_logic;"); pw.println("\t\ta_clk : in std_logic;"); pw.println("\t\ta_en : in std_logic;"); pw.println("\t\ta_wr : in std_logic;"); pw.println("\t\ta_addr : in std_logic_vector(" + mah + " downto 0);"); pw.println("\t\ta_din : in std_logic_vector(" + wh + " downto 0);"); pw.println("\t\ta_dout : out std_logic_vector(" + wh + " downto 0);"); pw.println("\t\tb_rst : in std_logic;"); pw.println("\t\tb_clk : in std_logic;"); pw.println("\t\tb_en : in std_logic;"); pw.println("\t\tb_wr : in std_logic;"); pw.println("\t\tb_addr : in std_logic_vector(" + mah + " downto 0);"); pw.println("\t\tb_din : in std_logic_vector(" + wh + " downto 0);"); pw.println("\t\tb_dout : out std_logic_vector(" + wh + " downto 0)"); pw.println("\t);"); pw.println("end " + module_name + ";"); pw.println(); pw.println("architecture rtl of " + module_name + " is"); pw.println(); pw.println("\tcomponent " + rtype + "_S" + cpw + "_S" + cpw); pw.println("\t\tport ("); pw.println("\t\t\tDIA : in std_logic_vector (" + ph + " downto 0);"); pw.println("\t\t\tDIB : in std_logic_vector (" + ph + " downto 0);"); pw.println("\t\t\tENA : in std_logic;"); pw.println("\t\t\tENB : in std_logic;"); pw.println("\t\t\tWEA : in std_logic;"); pw.println("\t\t\tWEB : in std_logic;"); pw.println("\t\t\t" + rsta + " : in std_logic;"); pw.println("\t\t\t" + rstb + " : in std_logic;"); if (ramb16 && (port_width >= 8)) { pw.println("\t\t\tDIPA : in std_logic_vector (" + pbh + " downto 0);"); pw.println("\t\t\tDIPB : in std_logic_vector (" + pbh + " downto 0);"); pw.println("\t\t\tDOPA : out std_logic_vector (" + pbh + " downto 0);"); pw.println("\t\t\tDOPB : out std_logic_vector (" + pbh + " downto 0);"); } pw.println("\t\t\tCLKA : in std_logic;"); pw.println("\t\t\tCLKB : in std_logic;"); pw.println("\t\t\tADDRA : in std_logic_vector (" + ah + " downto 0);"); pw.println("\t\t\tADDRB : in std_logic_vector (" + ah + " downto 0);"); pw.println("\t\t\tDOA : out std_logic_vector (" + ph + " downto 0);"); pw.println("\t\t\tDOB : out std_logic_vector (" + ph + " downto 0)"); pw.println("\t\t); "); pw.println("\tend component;"); pw.println(); if (init_ram) { pw.println("\tattribute INIT: string;"); int attribute_count = block / 256; for (int i = 0; i < attribute_count; i ++) { pw.println("\tattribute INIT_" + toHex(i, 2) + ": string;"); } pw.println(); for (int block_i = 0; block_i < block_count; block_i ++) { String block_bits = getBlockBits(binary_string, port_width, width, block_i); block_bits = padTrailingBitsToBlockSize(block_bits, block); String[] init_string = split(block_bits, 256); for (int i = 0; i < init_string.length; i++ ) { String init_hex = longBinaryStringToHex(reverse(init_string[i], port_width)); pw.println("\tattribute INIT_" + toHex(i, 2) + " of cmp_ram_" + block_i + ": label is \"" + init_hex + "\";"); } pw.println(); } } if (pad_addr) { pw.println("\tsignal p_a_addr : std_logic_vector (" + ah + " downto 0);"); pw.println("\tsignal p_b_addr : std_logic_vector (" + ah + " downto 0);"); pw.println(); } pw.println("begin"); pw.println(); if (pad_addr) { pw.println("\tp_a_addr <= \"" + zero(ah - mah) + "\" & a_addr;"); pw.println("\tp_b_addr <= \"" + zero(ah - mah) + "\" & b_addr;"); pw.println(); } for (int block_i = 0; block_i < block_count; block_i ++) { pw.println("\tcmp_ram_" + block_i + " : " + rtype + "_S" + cpw + "_S" + cpw); pw.println("\t\tport map ("); pw.println("\t\t\tWEA => a_wr,"); pw.println("\t\t\tWEB => b_wr,"); pw.println("\t\t\tENA => a_en,"); pw.println("\t\t\tENB => b_en,"); pw.println("\t\t\t" + rsta + " => a_rst,"); pw.println("\t\t\t" + rstb + " => b_rst,"); if (ramb16 && (port_width >= 8)) { pw.println("\t\t\tDIPA => \"" + pbhi + "\","); pw.println("\t\t\tDIPB => \"" + pbhi + "\","); pw.println("\t\t\tDOPA => open,"); pw.println("\t\t\tDOPB => open,"); } pw.println("\t\t\tCLKA => a_clk,"); pw.println("\t\t\tCLKB => b_clk,"); pw.println("\t\t\tDIA => a_din(" + ph + " downto " + pl + "),"); pw.println("\t\t\tADDRA => " + addr_prefix + "a_addr,"); pw.println("\t\t\tDOA => a_dout(" + ph + " downto " + pl + "),"); pw.println("\t\t\tDIB => b_din(" + ph + " downto " + pl + "),"); pw.println("\t\t\tADDRB => " + addr_prefix + "b_addr,"); pw.println("\t\t\tDOB => b_dout(" + ph + " downto " + pl + ")"); pw.println("\t\t);"); pw.println(); pl += port_width; ph += port_width; } pw.println("end rtl;"); pw.close(); System.out.print(block_count + " block"); if (block_count > 1) System.out.print("s"); System.out.println(" used (" + block + " bits/block)."); System.out.println("Done."); } catch (IOException e) { System.out.println(e.getMessage()); System.exit(-1); } } private static final int SEEK_GENERAL = 0; private static final int SEEK_ADDR = 1; private static final int SEEK_DATA = 2; /** * Parse a file in Altera MIF file format. * * @return String containing binary data * @throws IOException */ public String parseMifFile() throws IOException { FileReader fr = new FileReader(src_filename); StreamTokenizer st = new StreamTokenizer(fr); st.resetSyntax(); st.eolIsSignificant(true); st.slashStarComments(true); st.slashSlashComments(true); st.lowerCaseMode(true); st.commentChar('%'); st.commentChar('-'); st.wordChars('0', '9'); st.wordChars('a', 'z'); st.wordChars('A', 'Z'); st.wordChars('_', '_'); st.wordChars('[', '['); st.wordChars(']', ']'); st.wordChars('.', '.'); st.whitespaceChars(0, ' '); int state = SEEK_GENERAL; int address_radix = 16; int data_radix = 16; int addr = 0; int addr_range_lo = 0; int addr_range_hi = 0; boolean is_addr_range = false; long[] mem = null; while (st.nextToken() != StreamTokenizer.TT_EOF) { if (state == SEEK_GENERAL) { if ("width".equalsIgnoreCase(st.sval)) { st.nextToken(); st.nextToken(); if (!stringEmpty(st.sval)) { width = Integer.parseInt(st.sval); } } else if ("depth".equalsIgnoreCase(st.sval)) { st.nextToken(); st.nextToken(); if (!stringEmpty(st.sval)) { depth = Integer.parseInt(st.sval); mem = new long[depth]; } } else if ("address_radix".equalsIgnoreCase(st.sval)) { st.nextToken(); st.nextToken(); if ("bin".equalsIgnoreCase(st.sval)) address_radix = 2; else if ("dec".equalsIgnoreCase(st.sval)) address_radix = 10; else if ("hex".equalsIgnoreCase(st.sval)) address_radix = 16; else if ("oct".equalsIgnoreCase(st.sval)) address_radix = 8; } else if ("data_radix".equalsIgnoreCase(st.sval)) { st.nextToken(); st.nextToken(); if ("bin".equalsIgnoreCase(st.sval)) data_radix = 2; else if ("dec".equalsIgnoreCase(st.sval)) data_radix = 10; else if ("hex".equalsIgnoreCase(st.sval)) data_radix = 16; else if ("oct".equalsIgnoreCase(st.sval)) data_radix = 8; } else if ("begin".equalsIgnoreCase(st.sval)) { state = SEEK_ADDR; } } else if ((state == SEEK_ADDR) && (st.ttype == StreamTokenizer.TT_WORD)) { if (!stringEmpty(st.sval)) { if ("end".equalsIgnoreCase(st.sval)) { break; } else if (st.sval.startsWith("[")) { is_addr_range = true; addr_range_lo = Integer.parseInt(st.sval.substring(1, st.sval.indexOf('.')), address_radix); addr_range_hi = Integer.parseInt(st.sval.substring(st.sval.lastIndexOf('.') + 1, st.sval.length() - 1), address_radix); } else { is_addr_range = false; addr = Integer.parseInt(st.sval, address_radix); } state = SEEK_DATA; } } else if ((state == SEEK_DATA) && (st.ttype == StreamTokenizer.TT_EOL)) { state = SEEK_ADDR; } else if ((state == SEEK_DATA) && (st.ttype == StreamTokenizer.TT_WORD)) { if (!stringEmpty(st.sval)) { long data = Long.parseLong(st.sval, data_radix); if (is_addr_range) { for (addr = addr_range_lo; addr <= addr_range_hi; addr++) { if (addr >= 0 && addr < mem.length) mem[addr] = data; else { System.out.println("Warning: Data at address " + addr + " is outside the range of the specified depth."); } } } else { if (addr >= 0 && addr < mem.length) mem[addr] = data; else { System.out.println("Warning: Data at address " + addr + " is outside the range of the specified depth."); } } addr++; } } } fr.close(); StringBuffer binary_buffer = new StringBuffer(); for (int i = 0; i < mem.length; i ++) { binary_buffer.append(toBinary(mem[i], width)); } return binary_buffer.toString(); } /** * Brute force parse of a text file to extract hex data. * Good for files that only contain the desired hex data and whitespace or * delimeters such as commas or semicolons. * * @return String containing binary data. * @throws IOException */ public String parseTextFile() throws IOException { FileReader fr = new FileReader(src_filename); BufferedReader br = new BufferedReader(fr); StringBuffer hex_buffer = new StringBuffer(); String line = br.readLine(); while (line != null) { line = line.toUpperCase(); line = findAndReplace(line, "0X", ""); line = endBefore(line, "/"); line = filterNonHexDigits(line); if (!stringEmpty(line)) { hex_buffer.append(line); } line = br.readLine(); } br.close(); return longHexStringToBinary(hex_buffer.toString()); } /** * Test for whether a string is null or of zero length. * * @param str string to test * @return true if string is empty or null */ public static boolean stringEmpty(String str) { if (str == null) return true; if (str.length() == 0) return true; return false; } /** * Given a string with binary data, divide it into words of length <code>w</code> and * subdivide it based on the port width in <code>p_w</code> and then select which of the subdivisions * to extract given the value in <code>b_c</code>. */ public static String getBlockBits(String input, int p_w, int w, int b_c) { StringBuffer output = new StringBuffer(); int n_p = w / p_w; b_c = n_p - b_c - 1; for (int i = 0; i < input.length(); i += w) { int s = i + (b_c * p_w); int e = s + p_w; output.append(input.substring(s, e)); } return output.toString(); } /** * Reverse the input string by extracting blocks of length len and reordering them. * For example, if <code>len</code> equals <code>2</code> and <code>input</code> is * <code>A1B2C3D4E5</code>, then the return value will be <code>E5D4C3B2A1</code>. * @param input string to reverse * @param len length of blocks within string * @return reversed string */ public static String reverse(String input, int len) { StringBuffer output = new StringBuffer(); int i = input.length() - len; while (i >= 0) { output.append(input.substring(i, i + len)); i -= len; } return output.toString(); } /** * Split the input string into an array of strings of length <code>count</code> * @param input string to split * @param count character count to split at * @return array of strings */ public static String[] split(String input, int count) { ArrayList list = new ArrayList(); int i = 0; while (i < input.length()) { list.add(input.substring(i, Math.min(i + count, input.length()))); i += count; } return (String[])list.toArray(new String[]{}); } /** * Replace all instances of string <code>find</code> in the input string with string * <code>replace</code>. * @param input string to search * @param find string to search for * @param replace string to replace with * @return processed string */ public static String findAndReplace(String input, String find, String replace) { String output = input; int idx; while ((idx = output.indexOf(find)) != -1) { output = output.substring(0, idx) + replace + output.substring(idx+find.length()); } return output; } /** * Input parameter <code>terms<code> containst a set of <code>find</code> and <code>replace</code> pairs to * apply to the input string. * @param input string to search * @param terms hashmap of find and replace pairs * @return processed string */ public static String findAndReplace(String input, HashMap terms) { String output = input; for (Iterator i = terms.keySet().iterator(); i.hasNext();) { String find = i.next().toString(); String replace = terms.get(find).toString(); int idx; while ((idx = output.indexOf(find)) != -1) { output = output.substring(0, idx) + replace + output.substring(idx+find.length()); } //Pattern p = Pattern.compile(find, Pattern.CASE_INSENSITIVE); //Matcher m = p.matcher(output); //output = m.replaceAll(replace); } return output; } /** * Terminate the input string at the first instance of the string passed as <code>terminator</code>. * @param input string to terminate * @param terminator string to find in the input string and to terminate input before * @return terminated string */ public static String endBefore(String input, String terminator) { String output = input; int idx; if ((idx = output.indexOf(terminator)) != -1) { output = output.substring(0, idx); } return output; } /** * Remove any characters that are not 0 to 9 or A to F (case sensitive). * * @param input string to process for non-hex characters * @return string with non-hex characters removed */ public static String filterNonHexDigits(String input) { StringBuffer output = new StringBuffer(); for (int i = 0; i < input.length(); i ++) { char c = input.charAt(i); if (Character.isDigit(c) || ((c >= 'A') && (c <= 'F'))) { output.append(c); } } return output.toString(); } /** * Read a file into a string. * * @param file file to read * @return contents of file */ public static String readFileContents(File file) { String contents = null; try { FileInputStream fis = new FileInputStream(file); byte[] data = new byte[(int)file.length()]; int r = fis.read(data); contents = new String(data, 0, r); } catch (Exception e) { } return contents; } /** * Read a file into a string. * * @param file_name filename of file to read * @return contents of file */ public static String readFileContents(String file_name) { File file = new File(file_name); return readFileContents(file); } /** * Convert an integer into a hex string with correct zero padding for word aligment. * * @param i integer value to convert * @param word word size to align hex return value to * @return hex return value */ public static String toHex(int i, int word) { String result = Integer.toHexString(i); if ((result.length() % word) != 0) result = zero(word - (result.length() % word)) + result; return result; } /** * Convert an long integer into a binary string with correct zero padding for word aligment. * * @param n long integer value to convert * @param word word size to align hex return value to * @return hex return value */ public static String toBinary(long n, int word) { String result = Long.toBinaryString(n); if ((result.length() % word) != 0) result = zero(word - (result.length() % word)) + result; return result; } /** * Generate a string of zeros of the specified count * @param count number of zeroes to generate * @return string with the specified number of zeros */ public static String zero(int count) { StringBuffer result = new StringBuffer(); while (result.length() < count) result.append('0'); return result.toString(); } /** * Convert a hex string into a binary string with correct zero padding for word aligment. * Use for hex string of less than 8 characters. * @param hex input hex string * @param word word size to align binary return value to * @return binary return value */ public static String hexToBinary(String hex, int word) { int i = Integer.parseInt(hex, 16); return toBinary(i, word); } /** * Convert a hex string into a binary string with correct zero padding for word aligment. * Use for hex string of 8 or more characters. * @param hex input hex string * @return binary return value */ public static String longHexStringToBinary(String hex) { StringBuffer output = new StringBuffer(); for (int i = 0; i < hex.length(); i++) { String h = hex.substring(i, i + 1); output.append(hexToBinary(h, 4)); } return output.toString(); } /** * Convert a binary string into a hex string. * * @param input binary string * @return hex string */ public static String longBinaryStringToHex(String input) { StringBuffer output = new StringBuffer(); for (int i = 0; i < input.length(); i += 4) { String nibble = input.substring(i, i + 4); int n = 0; if (nibble.charAt(3) == '1') n += 1; if (nibble.charAt(2) == '1') n += 2; if (nibble.charAt(1) == '1') n += 4; if (nibble.charAt(0) == '1') n += 8; output.append(Integer.toHexString(n)); } return output.toString(); } /** * Given a binary string of a length less that <code>size</code>, append * trailing zeroes to pad it to the specified size. * @param input the input string * @return string with the correct length */ public static String padTrailingBitsToBlockSize(String input, int size) { StringBuffer output = new StringBuffer(input); if (output.length() < size) output.append(zero(size - output.length())); return output.toString(); } /** * Process the command line parameters * * @param clist command line parameters passed to main() method * @return true if options processed correctly */ public boolean processOptions( String clist[] ){ boolean success = true; for( int i = 0; i < clist.length; i++ ){ if ( clist[i].equals("-w") ){ width = Integer.parseInt(clist[ ++i ]); } else if ( clist[i].equals("-d") ){ depth = Integer.parseInt(clist[ ++i ]); } else if ( clist[i].equals("-b") ){ block = Integer.parseInt(clist[ ++i ]); } else if ( clist[i].equals("-m") ){ module_name = clist[ ++i ]; } else if ( clist[i].equals("-o") ){ dst_dir = clist[ ++i ]; } else if ( clist[i].equals("-pd") ){ preserve_depth = true; } else if ( clist[i].startsWith("-") ){ printUsage(); System.out.println("Unknown parameter: " + clist[i]); System.exit(-1); } else { if (stringEmpty(src_filename)) src_filename = clist[i]; else dst_filename = clist[i]; } } return success; } public static void printUsage() { System.out.println("BlockRAM VHDL Module Generator"); System.out.println("By Ed Anuff <ed@anuff.com>"); System.out.println("Version built: " + build_date); System.out.println(); System.out.println("Usage:"); System.out.println("java BlockGen [-w #] [-d #] [-b #] [-m name] [-o dir] [-pd] [src [dst]]"); System.out.println(); System.out.println("Generates a memory module in VHDL using Xilinx Synchronous Block RAM with"); System.out.println("the given parameters."); System.out.println(); System.out.println("Input file should be in Altera MIF (.mif) format or be a text file with"); System.out.println("hex data that can be extracted."); System.out.println(); System.out.println("If no input file is specified, an uninitialized memory module will be"); System.out.println("generated."); System.out.println(); System.out.println("Parameters:"); System.out.println(" -w : RAM data width in # of bits to a data word"); System.out.println(" -d : RAM address depth in # of words"); System.out.println(" -b : Size of Block RAM for the target device - "); System.out.println(" 4096 for Spartan2/2E/Virtex/E"); System.out.println(" 16384 for Spartan3/Virtex2/2Pro/2ProX"); System.out.println(" -m : Name of module generated"); System.out.println(" -o : Output directory"); System.out.println(" -pd : Preserve depth - Use specified address depth even"); System.out.println(" if larger address range is available in the Block RAM used."); System.out.println(); } public static void main(String[] args) { if (args.length < 1) { printUsage(); System.exit(-1); } BlockGen bgen = new BlockGen(args); bgen.process(); //System.out.println(hexToBinary("A3", 8)); } }