PicoBlaze
XilinxのFPGAには、SpartanII以上であれば8ビットの
マイクロコントローラPicoBlazeをインプリメントして
使えます。
PicoBlazeを利用すると、LCD、A/D変換器、D/A変換器
シリアル通信等のプロセッサを利用した方が、簡便に
実現できるシステムを、より短時間で実現できます。
PicoBlazeのハードウエア構成は、以下となっています。
プログラミングモデルで見ると汎用レジスタ、入力、出力
メモリがあるシンプルな構造です。
入力、出力は、各々256バイトあるので、入力→処理→出力が
単純になるように構成すると、超高速プロセッサになります。
演算をレジスタとレジスタ、レジスタとメモリに限定して
いるので、完全なRISCタイプではないですが、RISCタイプ
に近い構成になっています。
PicoBlazeの開発手順は、以下。
- ファームウエアを、アセンブリ言語で記述
- アセンブリ言語をアセンブラでアセンブルし、ROMモジュールに展開
- ROMモジュールとPicoBlazeのコンポーネントをプロジェクトに追加
- 論理合成、配置配線を実行
FPGAでシステムを構築する場合、最初の段階で
ファームウエア作成とROMモジュール作成が付加
されたイメージになります。
アセンブリ言語のニモニックは、以下。
ニモニックの内容から、フラグはZ、Cだけに限定されています。
大部分の命令は、オペランドにレジスタ、レジスタとレジスタと
即値の組み合わせで構成されています。
Z80やAVRのプログラムをアセンブリ言語で組んだ経験があれば
簡単にマスターできると思います。
PicoBlazeの動作を確認するため、入力から8ビットを取得し
反転後、出力に8ビット出力するアセンブリ言語プログラム
を作成しました。ブロック図で見ると簡単です。
アセンブリ言語プログラムは、以下。
;************************
; program ptst0.psm
;************************
;++++++++++++++++++++++++
; redefine register name
;++++++++++++++++++++++++
NAMEREG s0,GR0
NAMEREG s1,GR1
; NAMEREG s2,GR2
; NAMEREG s3,GR3
; NAMEREG s4,GR4
; NAMEREG s5,GR5
; NAMEREG s6,GR6
; NAMEREG s7,GR7
; NAMEREG s8,GR8
; NAMEREG s9,GR9
; NAMEREG sA,GRA
; NAMEREG sB,GRB
; NAMEREG sC,GRC
; NAMEREG sD,GRD
; NAMEREG sE,GRE
; NAMEREG sF,GRF
;++++++++++++++++++++++++
; I/O address
;++++++++++++++++++++++++
CONSTANT PIN,00
CONSTANT POUT,00
;++++++++++++++++++++++++
; entry address
;++++++++++++++++++++++++
ADDRESS 000
;++++++++++++++++++++++++
; set 0xff
;++++++++++++++++++++++++
INIT:
LOAD GR1,FF
;++++++++++++++++++++++++
; main loop
;++++++++++++++++++++++++
MAIN:
; get data from input port
INPUT GR0,PIN
; inverse
XOR GR0,GR1
; put data to output port
OUTPUT GR0,POUT
JUMP MAIN
PicoBlazeのアセンブリ言語では、数値は16進数2けたで
指定します。プログラムは、アドレス0から実行するので
「ADDRESS」擬似命令で指定します。
アセンブル、リンクは、「KCPSM3.EXE」を利用します。
このアセンブラを含めた開発ツールは、XilinxのPicoBlazeの
ページからダウンロードします。
ダウンロードするには、XilinxのWebサイトでユーザー登録が
必要になります。
会費やライセンス料を徴収されないので、ユーザー登録して
様々な情報を取得できるようにしておいた方がよいでしょう。
今回はSpartan3に関連するZip形式ファイルをダウンロードし
展開すると以下のディレクトリに必要ファイルが格納されて
いました。
アセンブル、リンクには、次のファイルが必要です。
KCPSM3.EXE
ROM_form.coe
ROM_form.vhd
ROM_form.v
kcpsm3.ngc
Zip形式ファイルを展開してできたディレクトリのサブ
ディレクトリの中に、これらのファイルは含まれている
ので、対象とするFPGAのプロジェクトファイルの中に
コピーして使います。
アセンブリ言語のソースコードを、アセンブルするには
以下のようにします。
アセンブラは、超高速で動くので、I/Oリダイレクトを使い
メッセージをファイルに入れ、エラーその他の情報を確認
できるようにします。
I/Oリダイレクトで格納された情報は、次のようになります。
KCPSM3 v1.30. Ken Chapman (Xilinx-UK) 2005
The assembler for KCPSM3 Programmable State Machine
PASS 1 - Reading input PSM file
;++++++++++++++++++++++++
; redefine register name
;++++++++++++++++++++++++
NAMEREG s0,GR0
NAMEREG s1,GR1
; NAMEREG s2,GR2
; NAMEREG s3,GR3
; NAMEREG s4,GR4
; NAMEREG s5,GR5
; NAMEREG s6,GR6
; NAMEREG s7,GR7
; NAMEREG s8,GR8
; NAMEREG s9,GR9
; NAMEREG sA,GRA
; NAMEREG sB,GRB
; NAMEREG sC,GRC
; NAMEREG sD,GRD
; NAMEREG sE,GRE
; NAMEREG sF,GRF
;++++++++++++++++++++++++
; I/O address
;++++++++++++++++++++++++
CONSTANT PIN,00
CONSTANT POUT,00
;++++++++++++++++++++++++
; entry address
;++++++++++++++++++++++++
ADDRESS 000
;++++++++++++++++++++++++
; set 0xff
;++++++++++++++++++++++++
INIT:
LOAD GR1,FF
DISABLE INTERRUPT
;++++++++++++++++++++++++
; main loop
;++++++++++++++++++++++++
MAIN:
; get data from input port
INPUT GR0,PIN
; inverse
XOR GR0,GR1
; put data to output port
OUTPUT GR0,POUT
JUMP MAIN
PASS 2 - Testing Instructions
;++++++++++++++++++++++++
; redefine register name
;++++++++++++++++++++++++
NAMEREG s0, GR0
NAMEREG s1, GR1
; NAMEREG s2,GR2
; NAMEREG s3,GR3
; NAMEREG s4,GR4
; NAMEREG s5,GR5
; NAMEREG s6,GR6
; NAMEREG s7,GR7
; NAMEREG s8,GR8
; NAMEREG s9,GR9
; NAMEREG sA,GRA
; NAMEREG sB,GRB
; NAMEREG sC,GRC
; NAMEREG sD,GRD
; NAMEREG sE,GRE
; NAMEREG sF,GRF
;++++++++++++++++++++++++
; I/O address
;++++++++++++++++++++++++
CONSTANT PIN, 00
CONSTANT POUT, 00
;++++++++++++++++++++++++
; entry address
;++++++++++++++++++++++++
ADDRESS 000
;++++++++++++++++++++++++
; set 0xff
;++++++++++++++++++++++++
INIT:
LOAD GR1, FF
DISABLE INTERRUPT
;++++++++++++++++++++++++
; main loop
;++++++++++++++++++++++++
MAIN:
; get data from input port
INPUT GR0, PIN
; inverse
XOR GR0, GR1
; put data to output port
OUTPUT GR0, POUT
JUMP MAIN
PASS 3 - Resolving addresses and line labels
000 ;++++++++++++++++++++++++
000 ; redefine register name
000 ;++++++++++++++++++++++++
000 NAMEREG s0, GR0
000 NAMEREG s1, GR1
000 ; NAMEREG s2,GR2
000 ; NAMEREG s3,GR3
000 ; NAMEREG s4,GR4
000 ; NAMEREG s5,GR5
000 ; NAMEREG s6,GR6
000 ; NAMEREG s7,GR7
000 ; NAMEREG s8,GR8
000 ; NAMEREG s9,GR9
000 ; NAMEREG sA,GRA
000 ; NAMEREG sB,GRB
000 ; NAMEREG sC,GRC
000 ; NAMEREG sD,GRD
000 ; NAMEREG sE,GRE
000 ; NAMEREG sF,GRF
000 ;++++++++++++++++++++++++
000 ; I/O address
000 ;++++++++++++++++++++++++
000 CONSTANT PIN, 00
000 CONSTANT POUT, 00
000 ;++++++++++++++++++++++++
000 ; entry address
000 ;++++++++++++++++++++++++
000 ADDRESS 000
000 ;++++++++++++++++++++++++
000 ; set 0xff
000 ;++++++++++++++++++++++++
000 INIT:
000 LOAD GR1, FF
001 DISABLE INTERRUPT
002 ;++++++++++++++++++++++++
002 ; main loop
002 ;++++++++++++++++++++++++
002 MAIN:
002 ; get data from input port
002 INPUT GR0, PIN
003 ; inverse
003 XOR GR0, GR1
004 ; put data to output port
004 OUTPUT GR0, POUT
005 JUMP MAIN
PASS 4 - Resolving Operands
000 ;++++++++++++++++++++++++
000 ; redefine register name
000 ;++++++++++++++++++++++++
000 NAMEREG s0, GR0
000 NAMEREG s1, GR1
000 ; NAMEREG s2,GR2
000 ; NAMEREG s3,GR3
000 ; NAMEREG s4,GR4
000 ; NAMEREG s5,GR5
000 ; NAMEREG s6,GR6
000 ; NAMEREG s7,GR7
000 ; NAMEREG s8,GR8
000 ; NAMEREG s9,GR9
000 ; NAMEREG sA,GRA
000 ; NAMEREG sB,GRB
000 ; NAMEREG sC,GRC
000 ; NAMEREG sD,GRD
000 ; NAMEREG sE,GRE
000 ; NAMEREG sF,GRF
000 ;++++++++++++++++++++++++
000 ; I/O address
000 ;++++++++++++++++++++++++
000 CONSTANT PIN, 00
000 CONSTANT POUT, 00
000 ;++++++++++++++++++++++++
000 ; entry address
000 ;++++++++++++++++++++++++
000 ADDRESS 000
000 ;++++++++++++++++++++++++
000 ; set 0xff
000 ;++++++++++++++++++++++++
000 INIT:
000 LOAD GR1, FF
001 DISABLE INTERRUPT
002 ;++++++++++++++++++++++++
002 ; main loop
002 ;++++++++++++++++++++++++
002 MAIN:
002 ; get data from input port
002 INPUT GR0, PIN
003 ; inverse
003 XOR GR0, GR1
004 ; put data to output port
004 OUTPUT GR0, POUT
005 JUMP MAIN
PASS 5 - Writing reformatted PSM file
ptst0.fmt
PASS 6 - Writing assembler log file
ptst0.log
PASS 7 - Writing coefficient file
ptst0.coe
PASS 8 - Writing VHDL memory definition file
ptst0.vhd
PASS 9 - Writing Verilog memory definition file
ptst0.v
PASS 10 - Writing System Generator memory definition file
ptst0.m
PASS 11 - Writing memory definition files
ptst0.hex
ptst0.dec
ptst0.mem
KCPSM3 successful.
KCPSM3 complete.
アセンブラは、15種類のファイルを生成すると参考資料に
書かれています。
VHDLでFPGA内部の回路を記述したとしても、VerilogHDL用ファイルも
生成されます。ISEでは、VHDL、VerilogHDLで記述した内容を、混在
させても構わないので、ファイルは残しておきます。
アセンブル、リンクが成功すると、プロジェクトにKCPSM3関係の
ファイルが出来ているので、追加していきます。
次の2つのVHDLコードを追加しました。
kcpsm3.vhd
ptst0.vhd
ptst0.vhdは、アセンブル、リンクで生成されたVHDLコードです。
KCPSM3が生成するVHDLコードは、ROMへのアクセス処理を記述した
マイクロプログラムになっています。
PicoBlazeは、1ワードが18ビットのプロセッサなので、18ビットの
中に、命令、アドレス、数値、レジスタ等の情報が埋込まれています。
これらの情報を利用して、実際に動くのはkcpsm3.vhdに記述した回路
になります。
kcpsm3.vhdのエンティティを眺めてみます。
entity kcpsm3 is
Port ( address : out std_logic_vector(9 downto 0);
instruction : in std_logic_vector(17 downto 0);
port_id : out std_logic_vector(7 downto 0);
write_strobe : out std_logic;
out_port : out std_logic_vector(7 downto 0);
read_strobe : out std_logic;
in_port : in std_logic_vector(7 downto 0);
interrupt : in std_logic;
interrupt_ack : out std_logic;
reset : in std_logic;
clk : in std_logic);
end kcpsm3;
エンティティの信号名から、わかることをリストしてみます。
- クロックとリセットが必要なので、外部から与える
- 割込み信号は、interruptで入力し、受付けるとinterrupt_ackで確認を出力
- 入出力は、in_portとout_portで8ビットで扱う
- 入力の場合、read_strobeを使い、読込み中を表示
- 出力の場合、write_strobeを使い、書込み中を表示
- 入出力が複数あるとき、どれかをport_idで指定
- 命令取得は、addressとinstructionを利用
- addressは10ビットあるので1024ワード分の命令を実行可能
- 命令1ワードは18ビット
FPGA内部に、PicoBlazeを入れる場合、componentで利用を宣言し
インスタンス指定で実体を作ってから、信号線を割当てます。
component指定
-- picoblaze component
component kcpsm3 is
port (
address : out std_logic_vector( 9 downto 0);
instruction : in std_logic_vector(17 downto 0);
port_id : out std_logic_vector( 7 downto 0);
write_strobe : out std_logic;
out_port : out std_logic_vector(7 downto 0);
read_strobe : out std_logic;
in_port : in std_logic_vector(7 downto 0);
interrupt : in std_logic;
interrupt_ack : out std_logic;
reset : in std_logic;
clk : in std_logic
);
end component ;
-- firmware component
component ptst0 is
port (
address : in std_logic_vector(9 downto 0);
instruction : out std_logic_vector(17 downto 0);
clk : in std_logic
);
end component ;
インスタンス指定
-- picoblaze instance
XPROCESSOR : kcpsm3 port map (
address => iADR ,
instruction => iInst ,
port_id => iPID ,
write_strobe => iWRSTB ,
out_port => iXDout ,
read_strobe => iRDSTB ,
in_port => iXDin ,
interrupt => iINTREQ ,
interrupt_ack => iINTACK ,
reset => iRST ,
clk => iCLK
);
-- firmware ROM instance
PTST0inst : ptst0 port map (
address => iADR ,
instruction => iInst ,
clk => iCLK
);
システム全体として動作するためのVHDLコードは、以下。
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity picotst0 is
port (
-- system
nRESET : in std_logic ;
CLOCK : in std_logic ; -- 48MHz
-- input
XDin : in std_logic_vector(7 downto 0) ;
-- output
XDout : out std_logic_vector(7 downto 0) ;
-- interrupt
XIREQ : in std_logic ;
XIACK : out std_logic ;
-- monitor clock
MCLK : out std_logic --;
) ;
end picotst0;
architecture Behavioral of picotst0 is
-- picoblaze component
component kcpsm3 is
port (
address : out std_logic_vector( 9 downto 0);
instruction : in std_logic_vector(17 downto 0);
port_id : out std_logic_vector( 7 downto 0);
write_strobe : out std_logic;
out_port : out std_logic_vector(7 downto 0);
read_strobe : out std_logic;
in_port : in std_logic_vector(7 downto 0);
interrupt : in std_logic;
interrupt_ack : out std_logic;
reset : in std_logic;
clk : in std_logic
);
end component ;
-- firmware component
component ptst0 is
port (
address : in std_logic_vector(9 downto 0);
instruction : out std_logic_vector(17 downto 0);
clk : in std_logic
);
end component ;
--
CONSTANT CNTMAX : integer := 47 ;
CONSTANT CNTHALF : integer := 24 ;
-- clock divider
signal iCNT : integer range 0 to CNTMAX ;
signal iCLK : std_logic ;
-- input
signal iXDin : std_logic_vector(7 downto 0) ;
-- output
signal iXDout : std_logic_vector(7 downto 0) ;
-- PicoBlaze
signal iADR : std_logic_vector(9 downto 0);
signal iInst : std_logic_vector(17 downto 0);
signal iPID : std_logic_vector(7 downto 0) ;
signal iRDSTB : std_logic ;
signal iWRSTB : std_logic ;
signal iINTREQ : std_logic ;
signal iINTACK : std_logic ;
signal iRST : std_logic ;
begin
-- input
iRST <= not nRESET ;
iINTREQ <= XIREQ ;
-- output
XIACK <= iINTACK ;
-- monitor clock out
MCLK <= iCLK ;
-- clock divider
-- generate 1 MHz
process (nRESET,CLOCK)
begin
if ( nRESET = '0' ) then
iCNT <= 0 ;
elsif rising_edge(CLOCK) then
if ( iCNT = CNTMAX ) then
iCNT <= 0 ;
else
iCNT <= iCNT + 1 ;
end if ;
end if ;
end process ;
iCLK <= '1' when ( iCNT < CNTHALF ) else '0' ;
-- input
process (iCLK)
begin
if rising_edge(iCLK) then
if ( iRDSTB = '1' and iPID = X"00" ) then
iXDin <= XDin ;
end if ;
end if ;
end process ;
-- output
process (iCLK)
begin
if rising_edge(iCLK) then
if ( iWRSTB = '1' and iPID = X"00" ) then
XDout <= iXDout ;
end if ;
end if ;
end process ;
-- picoblaze instance
XPROCESSOR : kcpsm3 port map (
address => iADR ,
instruction => iInst ,
port_id => iPID ,
write_strobe => iWRSTB ,
out_port => iXDout ,
read_strobe => iRDSTB ,
in_port => iXDin ,
interrupt => iINTREQ ,
interrupt_ack => iINTACK ,
reset => iRST ,
clk => iCLK
);
-- firmware ROM instance
PTST0inst : ptst0 port map (
address => iADR ,
instruction => iInst ,
clk => iCLK
);
end Behavioral;
入出力処理に、ポートのIDとストローブ信号を使います。
割込み関係の信号は、使わなくても、ダミーで外部に接続
しておきます。
UCFファイルの内容は、以下。
# system
NET "nRESET" LOC = "P73" ;
NET "CLOCK" LOC = "P55" ;
# input
NET "XDin<7>" LOC = "P10" ;
NET "XDin<6>" LOC = "P8" ;
NET "XDin<5>" LOC = "P7" ;
NET "XDin<4>" LOC = "P6" ;
NET "XDin<3>" LOC = "P5" ;
NET "XDin<2>" LOC = "P4" ;
NET "XDin<1>" LOC = "P2" ;
NET "XDin<0>" LOC = "P1" ;
# output
NET "XDout<7>" LOC = "P20" ;
NET "XDout<6>" LOC = "P18" ;
NET "XDout<5>" LOC = "P17" ;
NET "XDout<4>" LOC = "P15" ;
NET "XDout<3>" LOC = "P14" ;
NET "XDout<2>" LOC = "P13" ;
NET "XDout<1>" LOC = "P12" ;
NET "XDout<0>" LOC = "P11" ;
# interrupt
NET "XIREQ" LOC = "P23" ;
NET "XIACK" LOC = "P24" ;
# monitor clock
NET "MCLK" LOC = "P141" ;
PicoBlaze内蔵FPGAの動作は、次のようにテストしました。
8ビットのスイッチとLEDを接続して、スイッチの状態を
入力し、反転後、出力します。
写真では、わかりにくいですが、DIPスイッチが下がっている
ところに対応したLEDが点灯しています。
割込み処理
PicoBlazeはマイクロコンピュータなので、割込みが使えます。
タイマー割込みで、LEDの点灯パターンを変えていくプログラムを
作成しました。ブロック図は、以下。
ソースコードは、次のように記述しました。
;++++++++++++++++++++++++
; scratch SRAM
;++++++++++++++++++++++++
CONSTANT IFLAG,00 ; interrupt flag
CONSTANT STACK,3F
;++++++++++++++++++++++++
; I/O address
;++++++++++++++++++++++++
CONSTANT LED,00 ; LED port
CONSTANT AFF,01 ; acknowledge
;++++++++++++++++++++++++
; entry address
;++++++++++++++++++++++++
ADDRESS 000
;++++++++++++++++++++++++
; initialize
;++++++++++++++++++++++++
INIT:
; clear
XOR s0,s0
XOR sC,sC
; reset flag
STORE s0,IFLAG
;
ENABLE INTERRUPT
;
LOAD s1,01
;++++++++++++++++++++++++
; main loop
;++++++++++++++++++++++++
MAIN:
; get interrupt flag
LOAD s2,IFLAG
; masking
AND s2,s1
; judge
TEST s2,01
JUMP NZ,MAIN1
; update LED
OUTPUT sC,LED
; update value
ADD sC,01
MAIN1:
;
JUMP MAIN
;+++++++++++++++++++++++++++
; Interrupt Service Routine
;+++++++++++++++++++++++++++
ISR:
; push
STORE s0,STACK
; set flag
LOAD s0,01
STORE s0,IFLAG
; return acknowledge
OUTPUT s0,AFF
XOR s0,s0
OUTPUT s0,AFF
; pop
FETCH s0,STACK
;
RETURNI ENABLE
;+++++++++++++++++++++++++
; Interrupt Entry Address
;+++++++++++++++++++++++++
ADDRESS 3FF
JUMP ISR
割込み要求は、Interrupt信号を使います。
割込みはひとつだけで、受付けるとアドレス3FFに記述された
命令を実行します。3FFは、プログラムROMの最終アドレスに
なっているので、処理プログラム(割込みハンドラ)に分岐
するようにコードを記述します。
割込みハンドラでフラグを設定し、Interrupt信号を'H'から'L'に
落とします。割込みから戻るときに、RETURNI命令を使いますが
次の割込みを受付けられるように、オペランドをENABLEにします。
MAIN処理では、フラグを調べてセットされていればリセットします。
フラグがセットされているときには、カウンタ値をLEDに出力し
カウンタ値を更新します。
PicoBlazeのプログラムを作ったなら、割込みを扱う外部回路を
VHDLコードで定義していきます。
クロック分周器
システムクロックは48MHzなので、1MHzまで分周してPicoBlazeの
動作クロックにします。
CONSTANT CNTMAX : integer := 47 ;
CONSTANT CNTHALF : integer := 24 ;
signal iCNT : integer range 0 to CNTMAX ;
signal iCLK : std_logic ;
-- clock divider (generate 1MHz)
process (nRESET,CLOCK)
begin
if ( nRESET = '0' ) then
iCNT <= 0 ;
elsif rising_edge(CLOCK) then
if ( iCNT = CNTMAX ) then
iCNT <= 0 ;
else
iCNT <= iCNT + 1 ;
end if ;
end if ;
end process ;
iCLK <= '1' when ( iCNT < CNTHALF ) else '0' ;
PicoBlazeの動作クロック48MHzを分周して1kHzを生成し
シーケンサの動作クロックにします。
CONSTANT CCNTMAX : integer := 47_999 ;
CONSTANT CCNTHALF : integer := 24_000 ;
signal iCCNT : integer range 0 to CCNTMAX ;
signal iCCLK : std_logic ;
-- clock divider (generate 1kHz)
process (nRESET,iCLK)
begin
if ( nRESET = '0' ) then
iCCNT <= 0 ;
elsif rising_edge(iCLK) then
if ( iCNT = CNTMAX ) then
iCCNT <= 0 ;
else
iCCNT <= iCCNT + 1 ;
end if ;
end if ;
end process ;
iCCLK <= '1' when ( iCCNT < CNTHALF ) else '0' ;
1kHzを分周して2Hzを生成します。
CONSTANT SCNTMAX : integer := 499 ;
CONSTANT SCNTHALF : integer := 250 ;
signal iSCNT : integer range 0 to SCNTMAX ;
signal iSCLK : std_logic ;
-- clock divider (generate 2Hz)
process (nRESET,iCCLK)
begin
if ( nRESET = '0' ) then
iSCNT <= 0 ;
elsif rising_edge(iCCLK) then
if ( iSCNT = SCNTMAX ) then
iSCNT <= 0 ;
else
iSCNT <= iSCNT + 1 ;
end if ;
end if ;
end process ;
iSCLK <= '1' when ( iSCNT < SCNTHALF ) else '0' ;
シンクロナイザ
シーケンサを動かすためにトリガーを使いますが、2Hzを
シフトレジスタに入れ、rising_edgeをつかまえます。
signal iSFT : std_logic_vector(2 downto 0) ;
signal iTRG : std_logic ;
-- synchronizer (clock 1kHz)
process (nRESET,iCCLK)
begin
if ( nRESET = '0' ) then
iSFT <= "000" ;
elsif rising_edge(iCCLK) then
iSFT <= iSFT(1 downto 0) & iSCLK ;
end if ;
end process ;
iTRG <= '1' when ( iSFT = "011" ) else '0' ;
シーケンサ
PicoBlazeにInterrupt信号を与えるために、シーケンサを
利用します。Interrupt信号をフラグとして扱い、フラグを
リセットするカラクリを利用しているので、シーケンサを
使います。
signal iSTATE : std_logic_vector(1 downto 0) ;
signal iRTRG : std_logic ;
signal iINTERUPPT : std_logic ;
-- sequencer (clock 1kHz)
process (nRESET,iCCLK)
begin
if ( nRESET = '0' ) then
iSTATE <= "00" ;
elsif rising_edge(iCCLK) then
case conv_integer(iSTATE) is
-- wait trigger
when 0 => if ( iTRG = '1' ) then
iSTATE <= "01" ;
else
iSTATE <= "00" ;
end if ;
-- impress wait
when 1 => iSTATE <= "11" ;
-- wait reset flag
when 3 => if ( iRTRG = '1' ) then
iSTATE <= "10" ;
else
iSTATE <= "11" ;
end if ;
-- return first state
when 2 => iSTATE <= "00" ;
-- default
when others =>
iSTATE <= "00" ;
end case ;
end if ;
end process ;
iINTERUPPT <= iSTATE(0) ;
まとめると、次のVHDLコードとなります。
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity tstpint is
port (
-- system
nRESET : in std_logic ;
CLOCK : in std_logic ; -- 48MHz
-- interrupt
XIACK : out std_logic ;
-- LED output
LOUT : out std_logic_vector(7 downto 0) --;
) ;
end tstpint;
architecture Behavioral of tstpint is
-- picoblaze component
component kcpsm3 is
port (
address : out std_logic_vector( 9 downto 0);
instruction : in std_logic_vector(17 downto 0);
port_id : out std_logic_vector( 7 downto 0);
write_strobe : out std_logic;
out_port : out std_logic_vector(7 downto 0);
read_strobe : out std_logic;
in_port : in std_logic_vector(7 downto 0);
interrupt : in std_logic;
interrupt_ack : out std_logic;
reset : in std_logic;
clk : in std_logic
);
end component ;
-- firmware component
component ttt is
port (
address : in std_logic_vector(9 downto 0);
instruction : out std_logic_vector(17 downto 0);
clk : in std_logic
);
end component ;
--
CONSTANT CNTMAX : integer := 47 ;
CONSTANT CNTHALF : integer := 24 ;
CONSTANT CCNTMAX : integer := 47_999 ;
CONSTANT CCNTHALF : integer := 24_000 ;
CONSTANT SCNTMAX : integer := 499 ;
CONSTANT SCNTHALF : integer := 250 ;
-- clock divider (generate 1MHz)
signal iCNT : integer range 0 to CNTMAX ;
signal iCLK : std_logic ;
-- clock divider (generate 1kHz)
signal iCCNT : integer range 0 to CCNTMAX ;
signal iCCLK : std_logic ;
-- clock divider (generate 2Hz)
signal iSCNT : integer range 0 to SCNTMAX ;
signal iSCLK : std_logic ;
-- synchronizer (clock 1kHz)
signal iSFT : std_logic_vector(2 downto 0) ;
signal iTRG : std_logic ;
-- sequencer (clock 1kHz)
signal iSTATE : std_logic_vector(1 downto 0) ;
signal iRTRG : std_logic ;
signal iINTERUPPT : std_logic ;
-- input to PicoBlaze
signal iXDin : std_logic_vector(7 downto 0) := X"00" ;
-- output from PicoBlaze
signal iXDout : std_logic_vector(7 downto 0) ;
-- PicoBlaze
signal iADR : std_logic_vector(9 downto 0);
signal iInst : std_logic_vector(17 downto 0);
signal iPID : std_logic_vector(7 downto 0) ;
signal iRDSTB : std_logic := '0' ;
signal iWRSTB : std_logic ;
signal iINTACK : std_logic ;
signal iRST : std_logic ;
-- buffer
signal iLOUT : std_logic_vector(7 downto 0) ;
begin
-- input
iRST <= not nRESET ;
-- output
XIACK <= iINTACK ;
LOUT <= not iLOUT ;
-- clock divider (generate 1MHz)
process (nRESET,CLOCK)
begin
if ( nRESET = '0' ) then
iCNT <= 0 ;
elsif rising_edge(CLOCK) then
if ( iCNT = CNTMAX ) then
iCNT <= 0 ;
else
iCNT <= iCNT + 1 ;
end if ;
end if ;
end process ;
iCLK <= '1' when ( iCNT < CNTHALF ) else '0' ;
-- clock divider (generate 1kHz)
process (nRESET,CLOCK)
begin
if ( nRESET = '0' ) then
iCCNT <= 0 ;
elsif rising_edge(CLOCK) then
if ( iCNT = CNTMAX ) then
iCCNT <= 0 ;
else
iCCNT <= iCCNT + 1 ;
end if ;
end if ;
end process ;
iCCLK <= '1' when ( iCCNT < CNTHALF ) else '0' ;
-- clock divider (generate 2Hz)
process (nRESET,iCCLK)
begin
if ( nRESET = '0' ) then
iSCNT <= 0 ;
elsif rising_edge(iCCLK) then
if ( iSCNT = SCNTMAX ) then
iSCNT <= 0 ;
else
iSCNT <= iSCNT + 1 ;
end if ;
end if ;
end process ;
iSCLK <= '1' when ( iSCNT < SCNTHALF ) else '0' ;
-- synchronizer (clock 1kHz)
process (nRESET,iCCLK)
begin
if ( nRESET = '0' ) then
iSFT <= "000" ;
elsif rising_edge(iCCLK) then
iSFT <= iSFT(1 downto 0) & iSCLK ;
end if ;
end process ;
iTRG <= '1' when ( iSFT = "011" ) else '0' ;
-- sequencer (clock 1kHz)
process (nRESET,iCCLK)
begin
if ( nRESET = '0' ) then
iSTATE <= "00" ;
elsif rising_edge(iCCLK) then
case conv_integer(iSTATE) is
-- wait trigger
when 0 => if ( iTRG = '1' ) then
iSTATE <= "01" ;
else
iSTATE <= "00" ;
end if ;
-- impress wait
when 1 => iSTATE <= "11" ;
-- wait reset flag
when 3 => if ( iRTRG = '1' ) then
iSTATE <= "10" ;
else
iSTATE <= "11" ;
end if ;
-- return first state
when 2 => iSTATE <= "00" ;
-- default
when others =>
iSTATE <= "00" ;
end case ;
end if ;
end process ;
iINTERUPPT <= iSTATE(0) ;
-- PicoBlaze output
process (nRESET,iCLK)
begin
if ( nRESET = '0' ) then
iRTRG <= '0' ;
elsif rising_edge(iCLK) then
if ( iWRSTB = '1' ) then
if ( iPID = X"00" ) then
iLOUT <= iXDout ;
end if ;
if ( iPID = X"01" ) then
iRTRG <= iXDout(0) ;
end if ;
end if ;
end if ;
end process ;
-- picoblaze instance
XPROCESSOR : kcpsm3 port map (
address => iADR ,
instruction => iInst ,
port_id => iPID ,
write_strobe => iWRSTB ,
out_port => iXDout ,
read_strobe => iRDSTB ,
in_port => iXDin ,
interrupt => iINTERUPPT ,
interrupt_ack => iINTACK ,
reset => iRST ,
clk => iCLK
);
-- firmware ROM instance
TTTinst : ttt port map (
address => iADR ,
instruction => iInst ,
clk => iCLK
);
end Behavioral;
LEDの点灯パターンを出力するので、UCFの
内容は、単純にしました。
# system
NET "nRESET" LOC = "P73" ;
NET "CLOCK" LOC = "P55" ;
# output
NET "LOUT<7>" LOC = "P129" ;
NET "LOUT<6>" LOC = "P130" ;
NET "LOUT<5>" LOC = "P131" ;
NET "LOUT<4>" LOC = "P132" ;
NET "LOUT<3>" LOC = "P135" ;
NET "LOUT<2>" LOC = "P137" ;
NET "LOUT<1>" LOC = "P140" ;
NET "LOUT<0>" LOC = "P141" ;
# interrupt
NET "XIACK" LOC = "P24" ;
Spartan3の基板でテスト。
ダウンロードしたファイルの中に、割込みを
扱うアセンブリ言語コードがありました。
;Interrupt example
;
CONSTANT waveform_port, 02 ;bit0 will be data
CONSTANT counter_port, 04
CONSTANT pattern_10101010, AA
NAMEREG sA, interrupt_counter
;
start:
LOAD interrupt_counter,00 ;reset interrupt counter
LOAD s2,pattern_10101010 ;initial output condition
ENABLE INTERRUPT
;
drive_wave:
OUTPUT s2,waveform_port
LOAD s0,07 ;delay size
loop:
SUB s0,01 ;delay loop
JUMP NZ,loop
XOR s2,FF ;toggle waveform
JUMP drive_wave
;
ADDRESS 2B0
int_routine:
ADD interrupt_counter,01 ;increment counter
OUTPUT interrupt_counter,counter_port
RETURNI ENABLE
;
ADDRESS 3FF ;set interrupt vector
JUMP int_routine
このアセンブリ言語コードから、割込みが発生した
ときには、アドレス3FFの命令を実行することを把握
できました。
Z80CPUのモード1割込みでは、割込み発生で38hから
命令を実行します。この仕組みに似ているなという
印象をもちました。
相似な仕組みを掴まえてしまうと、プログラムを作成
しやすくなりました。
割込みで実行開始アドレスが唯一であるとき、複数の
割込みを使いたい場合、フラグを用意して並べておき
それらをINPUT命令で読み込んで、判定します。
ARM、PICでは、割込みの実行開始アドレスが唯一なので
割込みハンドラの中で、フラグを見て、対応する処理を
実行します。
ARMの場合、次のように割込みハンドラになります。
void IRQ_Handler(void) __irq
{
volatile UBYTE ch ;
/* judge UART receive interruption */
if ( (IRQSTA & UART_BIT) == UART_BIT ) {
/* judge */
if ( COMSTA0 & 1 ) {
/* clear flag */
ch = COMRX ;
*(sbuf+sindex) = ch ;
sindex++ ;
if ( ch == '\r' ) {
sindex = 0 ;
uflag = ON ;
}
}
}
/* judge timer0 interruption (10us) */
if ( (IRQSTA & RTOS_TIMER_BIT) == RTOS_TIMER_BIT ) {
/* clear timer1 interrupt flag */
T0CLRI = 0xff ;
/* increment */
tflag = ON ;
}
}
ニモニックコンバータ
PicoBlazeのアセンブリ言語ニモニックを覚えるのは面倒
なので、Pythonを利用し、Z80ニモニックから変換します。
Z80のアセンブラでエラーがなければ、Z80のシミュレータを
利用し、PicoBlazeの動作をシミュレートできます。
数種に分類し、変換処理を考えます。
Program Control
左右に、PicoBlaze、Z80のニモニックを並べてみます。
[PicoBlaze] [Z80]
JUMP aaa JP aaa
JUMP Z,aaa JP Z,aaa
JUMP NZ,aaa JP NZ,aaa
JUMP C,aaa JP C,aaa
JUMP NC,aaa JP NC,aaa
CALL aaa CALL aaa
CALL Z,aaa CALL Z,aaa
CALL NZ,aaa CALL NZ,aaa
CALL C,aaa CALL C,aaa
CALL NC,aaa CALL NC,aaa
RETURN RET
RETURN Z RET Z
RETURN NZ RET NZ
RETURN C RET C
RETURN NC RET NC
ニモニックのJP、RETURNを、JUMP、RETURNに
変換するだけで充分とわかります。
Input/Output
左右に、PicoBlaze、Z80のニモニックを並べてみます。
[PicoBlaze] [Z80]
INPUT sX,pp IN A,(pp)
INPUT sX,(sY) IN A,(C)
IN B,(C)
IN C,(C)
IN D,(C)
IN E,(C)
IN H,(C)
OUTPUT sX,pp OUT (pp),A
OUTPUT sX,(sY) OUT (C),A
OUT (C),B
OUT (C),C
OUT (C),D
OUT (C),E
OUT (C),H
Z80では、レジスタをI/Oポインタにする場合
レジスタはCと固定されているので、それに
合わせます。汎用レジスタは、A、B、C、D、E
H、Lなので、7レジスタをsA、sB、sC、sD、sE
sFに対応させます。Lレジスタを使わないように
します。
Logical
左右に、PicoBlaze、Z80のニモニックを並べてみます。
[PicoBlaze] [Z80]
LOAD sX,kk LD A,kk
LD B,kk
LD C,kk
LD D,kk
LD E,kk
LD H,kk
AND sX,kk AND kk
OR sX,kk OR kk
XOR sX,kk XOR kk
TEST sX,kk BIT kk,A
BIT kk,B
BIT kk,C
BIT kk,D
BIT kk,E
BIT kk,H
LOAD sX,(sY) LD A,A
LD A,B
LD A,C
LD A,D
LD A,E
LD A,H
LD B,A
LD B,B
LD B,C
LD B,D
LD B,E
LD B,H
LD C,A
LD C,B
LD C,C
LD C,D
LD C,E
LD C,H
LD D,A
LD D,B
LD D,C
LD D,D
LD D,E
LD D,H
LD E,A
LD E,B
LD E,C
LD E,D
LD E,E
LD E,H
LD H,A
LD H,B
LD H,C
LD H,D
LD H,E
LD H,H
AND sX,(sY) AND A
AND B
AND C
AND D
AND E
AND H
OR sX,(sY) OR A
OR B
OR C
OR D
OR E
OR H
XOR sX,(sY) XOR A
XOR B
XOR C
XOR D
XOR E
XOR H
TEST sX,(sY)
論理演算の結果格納レジスタは、レジスタA固定なので
s0を汎用レジスタのA、B、C、D、E、H、Lの内、7レジ
スタをsA、sB、sC、sD、sE、sFに対応させます。
Lレジスタは使いません。
Shift and Rotate
PicoBlazeのシフト、ローテート命令は、少し変わって
いますが、動作を考えると納得できます。
SR0 shift right with padding '0'
SR1 shift right with padding '1'
SRX shift right with carry
SRA arithmetic shift right
SL0 shift left with padding '0'
SL1 shift left with padding '1'
SLX shift left with carry
SLA arithmetic shift left
RR rotate right
RL rotate left
シフトで空いたビットに0を入れるか1を入れるか
を区別して操作できます。また、算術シフトで数値
の符号を変えないようにもできます。
さらに、キャリーフラグを含めてのシフトができる
ように工夫されています。
左右に、PicoBlaze、Z80のニモニックを並べてみます。
[PicoBlaze] [Z80]
SR0 sX SRL A
SRL B
SRL C
SRL D
SRL E
SRL H
SR1 sX
SRX sX
SRA sX SRA A
SRA B
SRA C
SRA D
SRA E
SRA H
RR sX RR A
RR B
RR C
RR D
RR E
RR H
SL0 sX
SL1 sX
SLX sX
SLA sX SLA A
SLA B
SLA C
SLA D
SLA E
SLA H
RL sX RL A
RL B
RL C
RL D
RL E
RL H
Z80の場合、シフト、ローテートともにキャリーを使うか
使わないかで命令を分けてあるので、完全に対応させる
ことはできません。
Arithmetic
左右に、PicoBlaze、Z80のニモニックを並べてみます。
[PicoBlaze] [Z80]
ADD sX,kk ADD A,kk
ADDCY sX,kk ADC A,kk
SUB sX,kk SUB kk
SUBCY sX,kk SBC kk
COMPARE sX,kk CP kk
ADD sX,sY ADD A
ADD B
ADD C
ADD D
ADD E
ADD H
ADDCY sX,sY ADC A
ADC B
ADC C
ADC D
ADC E
ADC H
SUB sX,sY SUB A
SUB B
SUB C
SUB D
SUB E
SUB H
SUBCY sX,sY SBC A
SBC B
SBC C
SBC D
SBC E
SBC H
COMPARE sX,sY CP A
CP B
CP C
CP D
CP E
CP H
Z80の加算、減算、比較の対象の一方は、レジスタA固定
なので、sXをsAとします。sYは、汎用レジスタのA、B、C
D、E、H、Lの内、7レジスタをsA、sB、sC、sD、sE、sFに
対応させます。Lレジスタは使いません。
Storage
左右に、PicoBlaze、Z80のニモニックを並べてみます。
[PicoBlaze] [Z80]
FETCH sX,ss LD A,(ss)
FETCH sX,(sY) LD A,(HL)
LD A,(IX)
LD A,(IY)
LD B,(HL)
LD B,(IX)
LD B,(IY)
LD C,(HL)
LD C,(IX)
LD C,(IY)
LD D,(HL)
LD D,(IX)
LD D,(IY)
LD E,(HL)
LD E,(IX)
LD E,(IY)
LD H,(HL)
LD H,(IX)
LD H,(IY)
STORE sX,ss LD (ss),A
STORE sX,(sY) LD (HL),A
LD (IX),A
LD (IY),A
LD (HL),B
LD (IX),B
LD (IY),B
LD (HL),C
LD (IX),C
LD (IY),C
LD (HL),D
LD (IX),D
LD (IY),D
LD (HL),E
LD (IX),E
LD (IY),E
LD (HL),H
LD (IX),H
LD (IY),H
Z80では、メモリポインタは、BC、DE、HL、IX、IYに
なりますが、命令に依存しない3レジスタペアのHL
IX、IYを利用します。HL、IX、IYをs7、s8、s9に対応
させます。
FETCHは、「行って取ってくる」という意味なので
メモリからレジスタに転送するLD命令を利用します。
STOREは、「行って保存する」という意味なので、レジ
スタ値をメモリに転送するLD命令を利用します。
Interrupt
左右に、PicoBlaze、Z80のニモニックを並べてみます。
[PicoBlaze] [Z80]
RETURNI ENABLE RETI
RETURNI DISABLE
ENABLE INTERRUPT EI
DISABLE INTERRUPT DI
Z80には、「RETURNI DISABLE」に相当する命令はないので
変換せずに、対応するニモニックに置換します。
PicoBlazeとZ80のニモニック対応がわかったので、Pythonで
スクリプトを作成します。
(under construction)
目次
前
次