帰らざるMOS回路(21)今時ゲートレベルでもあるまいに。やっぱりTEST Benchはいる

Joseph Halfmoon

今時ノスタルジックなゲートレベルのシミュレーションにハマりつつあります。前回はPWM回路を動かしてみましたが、素のSIM環境には不満が残りました。最低限の「テストベンチみたいなもの」がないと「シミュレーションやった感」が無いよな~と。あくまで「やった感」自己満足の世界ですが、そこが大事じゃないかと。

※ゲートレベルのシミュレーションに使わせていただいておりますツールは以下のものです。

Logisim

このツールは論理回路のお勉強用に画面上でインタラクティブにロジック動作をおいかけるためのツールです。論理検証に使うようなツールではありません。遅延もなく、HiZもなしです。

なかなか良く出来たUIで感銘を受けました。しかし残念なことに2014年10月で開発は “suspended indefinitely” です。上記からソフトウエアをダウンロードして動作させることはまだ可能でした(Windows10上で使用中)

ベスト・ベンチ「もどき」

回路図に階層を持たせる機能はあるので、

    • 最上位層にテストベンチ本体と被テスト回路(DUT)のインスタンスを置く
    • 被テスト回路(DUT)の最上位は1つのセルとして入出力ピンを持たせ、このレベルで全体シミュレーションをする

以下では中央上部の「8ビットPWM」という箱の中に前回作ったPWM回路がDUTとしておかれており、その外側でシミュレーションを制御しているのが「テストベンチ」部分です。

TESTBENCH_MAIN

テストベンチ「もどき」部分に設けた機能は以下のとおりです。

    1. 1相クロック CLK
    2. RESET信号生成器
    3. シミュレーションステップのカウンタ(16ビット)
    4. シミュレーション期間LED
    5. テストベクタ供給用ROM

1のクロックは「普通のエッジトリガの回路」用に1相の単純なクロックです。本シリーズでは、過去記事でノスタルジックな2相ノンオーバラップ・クロックをとりあげとりましたが、とりあえずシミュレータになれるまでは封印と。

2はRESET信号(回路図上ではRST、ハイ・アクティブ)を生成するためのテストベンチ回路です。現状、CLK信号がアクティブになるとRST信号も確定し、CLKが3発でるまでRSTはアクティブです。その間に必要な初期化をしてね、という感じです。勿論、テストベンチなので必要に応じてもっと長期間にするとかもアリです。

3はシミュレーション期間内での「時間位置」を知るためのカウンタです。CLKでインクリメント。とりあえず16ビットとしたので65535カウントまでOK。

4はシミュレーション期間中か終了しているかを示すLEDです。青点灯でRUN期間中。赤点灯で終了です。インタラクティブにステップ実行したり、所定の速さ(0.25Hz~4kHz)でRUNさせる機能はあるのですが、所定のクロック数バッチ実行したら終了、という機能が見当たりませんでした。そこで所定のクロック数を与えてLEDを点灯させることにいたしました。赤が点灯したら自分でクロックを止め、シミュレーション結果のLOGをクローズする必要があります。

5は、DUTによっては必要となる外部入力ベクトルを設定するためのメモリです。今回は未使用です。またそのうち。

DUT(Device Under Test)

今回は前回作ったPWM回路をDUT化してみました。こんな感じ。

DUT_PWM

シミュレーション結果

テストベンチの最上位層からシミュレーションし、5つの信号/バンドルをタブラー・フォーマットのファイルに出力してみました。ファイルの頭の方はこんな感じです。

CLK RST SIMCOUNT PWMOUT_PIN PWMCOUNTER_PIN
0 1 0000 0000 0000 0000 0 0000 0000
1 1 0000 0000 0000 0001 0 0000 0000
0 1 0000 0000 0000 0001 0 0000 0000
1 1 0000 0000 0000 0010 0 0000 0000
0 1 0000 0000 0000 0010 0 0000 0000
1 1 0000 0000 0000 0011 0 0000 0001
0 1 0000 0000 0000 0011 0 0000 0001
1 0 0000 0000 0000 0100 0 0000 0010
0 0 0000 0000 0000 0100 0 0000 0010
1 0 0000 0000 0000 0101 0 0000 0011
0 0 0000 0000 0000 0101 0 0000 0011
VCD波形ビューワーへの接続

今回から、ビット幅のある信号(バス、バンドル、レジスタ等)を表示できるようにしたので、Go言語による変換スクリプトを改造しました。とっても「ベタ」なプログラムなので、解読はしやすい?

package main

import (
    "bufio"
    "flag"
    "fmt"
    "os"
    s "strings"
    "time"
)

type vcdTable struct {
    titleLine []string
    nBits []int
    dataLine  [][]string
}

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func getBits(arg []string) ([]string, []int) {
    nItem := len(arg)
    tempS := make([]string, nItem)
    tempI := make([]int, nItem)
    for idx := 0; idx < nItem; idx++ {
        if len(arg[idx]) > 1 {
            work := s.Replace(arg[idx], " ", "", -1)
            tempS[idx] = work
            tempI[idx] = len(work)
        } else {
            tempS[idx] = arg[idx]
            tempI[idx] = 1
        }
    }
    return tempS, tempI
}

func (v *vcdTable) tableReader(fname string) {
    f, err := os.Open(fname)
    check(err)
    defer f.Close()
    scanner := bufio.NewScanner(f)
    header := true
    bitHeader := false
    for scanner.Scan() {
        lin := scanner.Text()
        linSlice := s.Split(lin, "\t")
        if header {
            v.titleLine = linSlice
            header = false
            bitHeader = true
        } else {
            sLis, iLis := getBits(linSlice)
            if bitHeader {
                v.nBits = iLis
                bitHeader = false
            }
            v.dataLine = append(v.dataLine, sLis)
        }
    }
}

func (v *vcdTable) vcdWriter(fname string, modname *string) {
    f, err := os.Create(fname)
    check(err)
    defer f.Close()
    w := bufio.NewWriter(f)
    wN := []string{"!", "\"", "#", "$", "%", "&", "'", "(", ")"}
    w.WriteString("$date\n")
    now := time.Now()
    w.WriteString("\t")
    w.WriteString(fmt.Sprint(now.Weekday()))
    w.WriteString(" ")
    w.WriteString(fmt.Sprint(now.Month()))
    w.WriteString(" ")
    w.WriteString(fmt.Sprint(now.Day()))
    w.WriteString(" ")
    w.WriteString(fmt.Sprint(now.Hour()))
    w.WriteString(":")
    w.WriteString(fmt.Sprint(now.Minute()))
    w.WriteString(":")
    w.WriteString(fmt.Sprint(now.Second()))
    w.WriteString(" ")
    w.WriteString(fmt.Sprint(now.Year()))
    w.WriteString("\n")
    w.WriteString("$end\n")
    w.WriteString("$version\n")
    w.WriteString("\tLogisim\n")
    w.WriteString("$end\n")
    w.WriteString("$timescale\n")
    w.WriteString("\t100ns\n")
    w.WriteString("$end\n")
    w.WriteString("$scope module ")
    w.WriteString(*modname)
    w.WriteString(" $end\n")
    for idx, fil := range v.titleLine {
        if v.nBits[idx] > 1 {
            w.WriteString("$var wire ")
            w.WriteString(fmt.Sprint(v.nBits[idx]))
            w.WriteString(" ")
            w.WriteString(wN[idx])
            w.WriteString(" ")
            w.WriteString(fil)
            w.WriteString(" [")
            w.WriteString(fmt.Sprint(v.nBits[idx]))
            w.WriteString(":0] ")
            w.WriteString(" $end\n")
        } else {
            w.WriteString("$var wire 1 ")
            w.WriteString(wN[idx])
            w.WriteString(" ")
            w.WriteString(fil)
            w.WriteString(" $end\n")
        }
    }
    w.WriteString("$upscope $end\n")
    w.WriteString("$enddefinitions $end\n")
    w.WriteString("#0\n")
    w.WriteString("$dumpvars\n")
    for linN, linBody := range v.dataLine {
        w.WriteString("#")
        w.WriteString(fmt.Sprint((linN + 1) * 100))
        w.WriteString("\n")
        for idx, fil := range linBody {
            if v.nBits[idx] > 1 {
                w.WriteString("b")
                w.WriteString(fil)
                w.WriteString(" ")
                w.WriteString(wN[idx])
                w.WriteString("\n")
            } else {
                w.WriteString(fil)
                w.WriteString(wN[idx])
                w.WriteString("\n")
            }
        }
    }
    w.Flush()
}

func main() {
    opt1 := flag.Bool("OPT", false, "a bool")
    optFile := flag.String("FIELD", "", "a file name")
    modName := flag.String("MOD", "DUT", "a module name")
    flag.Parse()
    fmt.Println("OPT: ", *opt1)
    fmt.Println("FIELD: ", *optFile)
    fmt.Println("MOD: ", *modName)
    fnameSlice := flag.Args()
    fmt.Println("fnameSlice: ", fnameSlice)
    if len(fnameSlice) > 1 {
        fmt.Println("Table filename: ", fnameSlice[0])
        fmt.Println("VCD filename: ", fnameSlice[1])
        var vt vcdTable
        vt.tableReader(fnameSlice[0])
        vt.vcdWriter(fnameSlice[1], modName)
        fmt.Println(vt.titleLine)
        fmt.Println(vt.dataLine[0])
    } else {
        fmt.Println("Two File names needed.")
    }
}

タブラーフォーマットのファイル TABLE.tbl から VCDフォーマットのファイル dut.vcd を生成するには以下のようにします。

$ go run table2VCD.go TABLE.tbl dut.vcd

さてフリーのVCDビューワーの定番 gtkwave で波形を観察したところが以下に。

VCDwaveform

 

テストベンチ作るので1回使ってしまったです。

帰らざるMOS回路(20)今時ゲートレベル論理SIMでもあるまいに。リハビリのPWM。に戻る

帰らざるMOS回路(22)今時ゲートレベルでもあるまいに。ということでVerilogも? へ進む