Shows an implementation of the LC-3 instruction set in Verilog HDL. Includes test benches and simulation results.
Implementation
One dark Oregon winter afternoon, I said “Let’s build a micro processor”. What started as a noble thought became a rather intense but fun project.
This section describes the implementation of the LC-3 using a Field Programmable Logic Array. An FPGA is an array blocks with basic functionality such as Lookup table, a full adder and a flip-flop. For more information on FPGAs refer to the section Programmable Logic in the inquiry “How do computers do math?“.
The FPGA used to implement the LC-3 microprocessor is a Xilinx Spartan6, but others will fit equally well. My choice was inspired by the pricing of the development board and the fairly good free development tools. Other choices would be Altera for the FPGA, their IDE or Icarus Verilog for the synthesizer and simulator and GTKWave for the waveform viewer. Refer to the end of this article for links and references to introductory Verilog books.
Schematic
The top level schematic is shown below. The modules are defined using Verilog, an hardware description language (HDL) used to model digital logic.
This is my first Verilog implementation, please bear with me ..
State
State.v
Implementation of the LC-3 instruction set in Verilog, source file State.v
:
cCtrl, // controller control signal input eREADY, // external memory ready signal output wire pEn, // update PC enable output wire fEn, // fetch output enable output wire dEn, // decode enable output wire [2:0] mOp, // memory operation selector output wire rWe ); // register write enable `include "UpdatePC.vh" `include "Fetch.vh" `include "Decode.vh" `include "Registers.vh" `include "MemoryIF.vh" parameter [3:0] STATE_UPDATEPC = 4'd0, // update program counter STATE_FETCH = 4'd1, // fetch instruction STATE_DECODE = 4'd2, // decode STATE_ALU = 4'd3, // ALU STATE_ADDRNPC = 4'd4, // calc tPC address STATE_ADDRMEM = 4'd5, // calc memory address STATE_INDMEM = 4'd6, // indirect memory address STATE_RDMEM = 4'd7, // read memory STATE_WRMEM = 4'd8, // write memory STATE_WRREG = 4'd9, // write register STATE_ILLEGAL = 4'd15; // illegal state parameter EREADY_INA = 1'b0, // external memory not ready EREADY_ACT = 1'b1, // external memory ready EREADY_X = 1'bx; wire [1:0] iType = cCtrl[4:3]; // instruction type (00=alu, 01=ctrl, 10=mem) wire [1:0] maType = cCtrl[2:1]; // memory access type (00=indaddr, 01=read, 02=write, 03=updreg) wire indType = cCtrl[0]; // indirect memory access type reg [3:0] state; // current state reg [3:0] nState; // next state reg [6:0] out; // current output signals reg [6:0] nOut; // next output signals assign pEn = out[6]; assign fEn = out[5]; assign dEn = out[4]; assign mOp = out[3:1]; assign rWe = out[0]; // the combinational logic always @(state, eREADY, iType, maType, indType, state, out) casex ({state, eREADY, iType, maType, indType}) {STATE_UPDATEPC, EREADY_X, ITYPE_X, MATYPE_X, INDTYPE_X} : begin nState = STATE_FETCH; nOut = {PEN_0, FEN_1, DEN_0, MOP_NONE, RWE_0}; end {STATE_FETCH, EREADY_ACT, ITYPE_X, MATYPE_X, INDTYPE_X} : begin nState = STATE_DECODE; nOut = {PEN_0, FEN_0, DEN_1, MOP_NONE, RWE_0}; end {STATE_DECODE, EREADY_X, ITYPE_ALU, MATYPE_X, INDTYPE_X} : begin nState = STATE_ALU; nOut = {PEN_0, FEN_0, DEN_0, MOP_NONE, RWE_0}; end {STATE_DECODE, EREADY_X, ITYPE_CTL, MATYPE_X, INDTYPE_X} : begin nState = STATE_ADDRNPC; nOut = {PEN_0, FEN_0, DEN_0, MOP_NONE, RWE_0}; end {STATE_DECODE, EREADY_X, ITYPE_MEM, MATYPE_X, INDTYPE_X} : begin nState = STATE_ADDRMEM; nOut = {PEN_0, FEN_0, DEN_0, MOP_NONE, RWE_0}; end {STATE_ADDRMEM, EREADY_X, ITYPE_X, MATYPE_IND, INDTYPE_X} : begin nState = STATE_INDMEM; nOut = {PEN_0, FEN_0, DEN_0, MOP_RD, RWE_0}; end {STATE_ADDRMEM, EREADY_X, ITYPE_X, MATYPE_RD, INDTYPE_X} : begin nState = STATE_RDMEM; nOut = {PEN_0, FEN_0, DEN_0, MOP_RD, RWE_0}; end {STATE_INDMEM, EREADY_ACT, ITYPE_X, MATYPE_X, INDTYPE_RD} : begin nState = STATE_RDMEM; nOut = {PEN_0, FEN_0, DEN_0, MOP_RDI, RWE_0}; end {STATE_ADDRMEM, EREADY_X, ITYPE_X, MATYPE_WR, INDTYPE_X} : begin nState = STATE_WRMEM; nOut = {PEN_0, FEN_0, DEN_0, MOP_WR, RWE_0}; end {STATE_INDMEM, EREADY_ACT, ITYPE_X, MATYPE_X, INDTYPE_WR} : begin nState = STATE_WRMEM; nOut = {PEN_0, FEN_0, DEN_0, MOP_WR, RWE_0}; end {STATE_ALU, EREADY_X, ITYPE_X, MATYPE_X, INDTYPE_X} : begin nState = STATE_WRREG; nOut = {PEN_0, FEN_0, DEN_0, MOP_NONE, RWE_1}; end {STATE_ADDRMEM, EREADY_X, ITYPE_X, MATYPE_REG, INDTYPE_X} : begin nState = STATE_WRREG; nOut = {PEN_0, FEN_0, DEN_0, MOP_NONE, RWE_1}; end {STATE_RDMEM, EREADY_ACT, ITYPE_X, MATYPE_X, INDTYPE_X} : begin nState = STATE_WRREG; nOut = {PEN_0, FEN_0, DEN_0, MOP_NONE, RWE_1}; end {STATE_WRMEM, EREADY_ACT, ITYPE_X, MATYPE_X, INDTYPE_X} : begin nState = STATE_UPDATEPC; nOut = {PEN_1, FEN_0, DEN_0, MOP_NONE, RWE_0}; end {STATE_WRREG, EREADY_X, ITYPE_X, MATYPE_X, INDTYPE_X} : begin nState = STATE_UPDATEPC; nOut = {PEN_1, FEN_0, DEN_0, MOP_NONE, RWE_0}; end {STATE_ADDRNPC, EREADY_X, ITYPE_X, MATYPE_X, INDTYPE_X} : begin nState = STATE_UPDATEPC; nOut = {PEN_1, FEN_0, DEN_0, MOP_NONE, RWE_0}; end {STATE_FETCH, EREADY_INA, ITYPE_X, MATYPE_X, INDTYPE_X} : begin nState = state; nOut = out; end {STATE_INDMEM, EREADY_INA, ITYPE_X, MATYPE_X, INDTYPE_X} : begin nState = state; nOut = out; end {STATE_RDMEM, EREADY_INA, ITYPE_X, MATYPE_X, INDTYPE_X} : begin nState = state; nOut = out; end {STATE_WRMEM, EREADY_INA, ITYPE_X, MATYPE_X, INDTYPE_X} : begin nState = state; nOut = out; end default : begin nState = STATE_ILLEGAL; nOut = {PEN_0, FEN_0, DEN_0, MOP_NONE, RWE_0}; end endcase // the sequential logic always @(negedge clock, posedge reset) if (reset) begin state <= STATE_UPDATEPC; out <= {PEN_0, FEN_0, DEN_0, MOP_NONE, RWE_0}; end else begin state <= nState; out <= nOut; end; endmodule[/code] </p> <h3> Decode </h3> <h4> Decode.vh </h4> <p> Implementation of the LC-3 instruction set in Verilog, header file <code>Decode.vh</code>: parameter DEN_0 = 1'b0, // Decode enable DEN_1 = 1'b1; parameter [1:0] ITYPE_ALU = 2'b00, // generalized instruction type ITYPE_CTL = 2'b01, ITYPE_MEM = 2'b10, ITYPE_HLT = 2'b11, ITYPE_X = 2'bxx; parameter [1:0] MATYPE_IND = 2'b00, // generalized memory access type MATYPE_RD = 2'b01, MATYPE_WR = 2'b10, MATYPE_REG = 2'b11, MATYPE_X = 2'bxx; parameter INDTYPE_WR = 1'b0, // generalized memory indirection type INDTYPE_RD = 1'b1, INDTYPE_X = 1'bx;
Decode.v
Implementation of the LC-3 instruction set in Verilog, source file Decode.v
:
module Decode( input clock, input reset, input en, // input enable input [15:0] eDIN, // external memory data input input [2:0] psr, // processor status register output [4:0] cCtrl, // various control signals output [1:0] drSrc, // selects what to write to DR output [2:0] uOp, // selecta ALU operation output aOp, // selects Address operation output pNext, // selects if PC should branch output [2:0] sr1ID, // source register 1 ID output [2:0] sr2ID, // source register 2 ID output [2:0] drID, // destination register ID output wire [4:0] imm, // lower 5 bits from IR value output wire [8:0] offset ); // lower 9 bits from IR value `include "ALU.vh" `include "Address.vh" `include "MemoryIF.vh" `include "DrMux.vh" `include "UpdatePC.vh" `include "Decode.vh" parameter [2:0] ID_X = 3'bxxx; // Instruction Register (ir) // read instruction from external memory bus (after Fetch initiated the bus cycle) reg [15:0] ir; assign imm = ir[4:0]; // output the lower 5 bits assign offset = ir[8:0]; // output the lower 9 bits always @(posedge clock, posedge reset) if (reset) ir = 16'hffff; else if (en == DEN_1) ir = eDIN; parameter [3:0] // opcodes for the instructions I_BR = 4'b0000, I_ADD = 4'b0001, I_LD = 4'b0010, I_ST = 4'b0011, I_AND = 4'b0101, I_LDR = 4'b0110, I_STR = 4'b0111, I_NOT = 4'b1001, I_LDI = 4'b1010, I_STI = 4'b1011, I_JMP = 4'b1100, I_LEA = 4'b1110, I_HLT = 4'b1111; reg [20:0] ctl; // current control signal bundle // untangle control signal bundle assign cCtrl = ctl[ 20:16 ]; // { iType, maType, indRd } assign uOp = ctl[ 15:13 ]; assign aOp = ctl[ 12 ]; assign drSrc = ctl[ 11:10 ]; assign pNext = ctl[ 9 ]; assign drID = ctl[ 8: 6 ]; assign sr1ID = ctl[ 5: 3 ]; assign sr2ID = ctl[ 2: 0 ]; // combinational logic to determine control signals wire [2:0] uOpAddC = (ir[5]) ? UOP_ADDIMM : UOP_ADDREG; // candidate for uOp in case of ADD instruction wire [2:0] uOpAndC = (ir[5]) ? UOP_ANDIMM : UOP_ANDREG; // candidate for uOp in case of AND instruction wire pNextC = |(ir[11:9] & psr) ? PNEXT_TPC : PNEXT_NPC; // candidate for pNext in case of BR instruction always @(ir[15:12], uOpAddC, uOpAndC, pNextC) // State State State ALU Address DrSource UpdatePC Registers RegistersRegisters case (ir[15:12])// iType maType indType uOp aOp drSrc pNext drID sr1ID sr2ID I_ADD : ctl = {ITYPE_ALU, MATYPE_X, INDTYPE_X, uOpAddC, AOP_X, DRSRC_ALU, PNEXT_NPC, ir[11:9], ir[8:6], ir[2:0] }; I_AND : ctl = {ITYPE_ALU, MATYPE_X, INDTYPE_X, uOpAndC, AOP_X, DRSRC_ALU, PNEXT_NPC, ir[11:9], ir[8:6], ir[2:0] }; I_NOT : ctl = {ITYPE_ALU, MATYPE_X, INDTYPE_X, UOP_NOT, AOP_X, DRSRC_ALU, PNEXT_NPC, ir[11:9], ir[8:6], ID_X }; I_BR : ctl = {ITYPE_CTL, MATYPE_X, INDTYPE_X, UOP_X, AOP_NPC, DRSRC_X, pNextC, ID_X, ID_X, ID_X }; I_JMP : ctl = {ITYPE_CTL, MATYPE_X, INDTYPE_X, UOP_X, AOP_SR1, DRSRC_X, PNEXT_TPC, ID_X, ir[8:6], ID_X }; I_LD : ctl = {ITYPE_MEM, MATYPE_RD, INDTYPE_X, UOP_X, AOP_NPC, DRSRC_MEM, PNEXT_NPC, ir[11:9], ID_X, ID_X }; I_LDR : ctl = {ITYPE_MEM, MATYPE_RD, INDTYPE_X, UOP_X, AOP_SR1, DRSRC_MEM, PNEXT_NPC, ir[11:9], ir[8:6], ID_X }; I_LDI : ctl = {ITYPE_MEM, MATYPE_IND, INDTYPE_RD, UOP_X, AOP_NPC, DRSRC_MEM, PNEXT_NPC, ir[11:9], ID_X, ID_X }; I_LEA : ctl = {ITYPE_MEM, MATYPE_REG, INDTYPE_X, UOP_X, AOP_NPC, DRSRC_ADDR, PNEXT_NPC, ir[11:9], ID_X, ID_X }; I_ST : ctl = {ITYPE_MEM, MATYPE_WR, INDTYPE_X, UOP_X, AOP_NPC, DRSRC_X, PNEXT_NPC, ID_X, ID_X, ir[11:9]}; I_STR : ctl = {ITYPE_MEM, MATYPE_WR, INDTYPE_X, UOP_X, AOP_SR1, DRSRC_X, PNEXT_NPC, ID_X, ir[8:6], ir[11:9]}; I_STI : ctl = {ITYPE_MEM, MATYPE_IND, INDTYPE_WR, UOP_X, AOP_NPC, DRSRC_X, PNEXT_NPC, ID_X, ID_X, ir[11:9]}; default : ctl = {ITYPE_HLT, MATYPE_X, INDTYPE_X, UOP_X, AOP_X, DRSRC_X, PNEXT_X, ID_X, ID_X, ID_X }; endcase endmodule
UpdatePC
UpdatePC.vh
Implementation of the LC-3 instruction set in Verilog, header file UpdatePC.vh
:
parameter PNEXT_NPC = 1'b0, // UpdatePC branch signal PNEXT_TPC = 1'b1, PNEXT_X = 1'bx; parameter PEN_0 = 1'b0, // UpdatePC enable PEN_1 = 1'b1;
UpdatePC.v
Implementation of the LC-3 instruction set in Verilog, source file UpdatePC.v
:
module UpdatePC( input clock, input reset, input en, // enable signal input [15:0] tPC, // target program counter input pNext, // if 1 then branch to tPC output reg [15:0] pc, // program counter output reg [15:0] nPC ); // next program counter (pc+1) `include "UpdatePC.vh" wire [15:0] a = (pNext) ? tPC : nPC; // if pNext==1, then jump to tPC wire [15:0] b = (en == PEN_1) ? a : pc; // change PC only in "Update PC" state wire [15:0] c = b + 1'b1; // use carry input always @(posedge clock, posedge reset) if (reset) begin pc <= 16'h3000; nPC <= 16'h3001; end else begin pc <= b; nPC <= c; end; endmodule[/code] </p> <h3> Fetch </h3> <h4> Fetch.vh </h4> <p> Implementation of the LC-3 instruction set in Verilog, header file <code>Fetch.vh</code>: parameter FEN_0 = 1'b0, // fetch enable FEN_1 = 1'b1;
Fetch.v
Implementation of the LC-3 instruction set in Verilog, source file Fetch.v
:
module Fetch( input en, // output enable input [15:0] pc, // program counter output reg iBR, // internal memory address lines output reg [15:0] iADDR, // internal memory address lines output reg iWEA ); // internal memory write enable `include "Fetch.vh" always @(en, pc) begin iBR <= ( en == FEN_1 ) ? 1'b1 : 1'b0; iADDR <= ( en == FEN_1 ) ? pc : 16'hxxxx; iWEA <= ( en == FEN_1 ) ? 1'b0 : 1'bx; end endmodule[/code] </p> <h3> Registers </h3> <h4> Registers.vh </h4> <p> Implementation of the LC-3 instruction set in Verilog, header file <code>Registers.vh</code>: parameter RWE_0 = 1'b0, // register write enable RWE_1 = 1'b1; parameter [2:0] PSR_POSITIVE = 3'b001, // processor status register bits PSR_ZERO = 3'b010, // should match BR instruction PSR_NEGATIVE = 3'b100;
Registers.v
Implementation of the LC-3 instruction set in Verilog, source file Registers.v
:
module Registers( input clock, input reset, input we, // write enable input [2:0] sr1ID, // source register 1 ID input [2:0] sr2ID, // source register 2 ID input [2:0] drID, // destination register ID input [15:0] dr, // destination register value output reg [15:0] sr1, // source register 1 value output reg [15:0] sr2, // source register 2 value output reg [2:0] psr ); // processor status register `include "Registers.vh" reg [3:0] id; reg [15:0] gpr [0:7]; // general purpose registers // write the destination register value, and update Process Status Register (psr) always @(posedge clock, posedge reset) if (reset) for (id = 0; id < 7; id = id + 1) // initial all registers to 0 gpr[ id ] <= 16'h0000; else if (we == RWE_1) // when enabled by the FSM begin if (dr[ 15 ]) // update processor status register (neg,zero,pos) psr <= PSR_NEGATIVE; else if (|dr) psr <= PSR_POSITIVE; else psr <= PSR_ZERO; gpr[ drID ] <= dr; // write the value dr to the register identified by drID end // output the value of the register identified by "sr1ID" on output "sr1" // output the value of the register identified by "sr2ID" on output "sr2" always @(sr1ID, sr2ID, gpr[ sr1ID ], gpr[ sr2ID ]) begin sr1 = gpr[ sr1ID ]; sr2 = gpr[ sr2ID ]; end endmodule[/code] </p> <h3> ALU </h3> <h4> ALU.vh </h4> <p> parameter [2:0] UOP_ADDREG = 3'b000, // ALU operation UOP_ADDIMM = 3'b001, UOP_ANDREG = 3'b010, UOP_ANDIMM = 3'b011, UOP_NOT = 3'b100, UOP_X = 3'bxxx;
ALU.v
module ALU( input [2:0] uOp, // operation selector input [15:0] sr1, // source register 1 value (SR1) input [15:0] sr2, // source register 2 value (SR2) input [4:0] imm, // lower 5 bits from instruction register output reg [15:0] uOut ); // result of ALU operation `include "ALU.vh" wire [15:0] imm5 = ({ {11{imm[4]}}, imm[4:0] }); // sign extend to 16 bits always @(uOp or sr1 or sr2 or imm5) casex (uOp) 3'b000: uOut = sr1 + sr2; // ADD Mode 0 3'b001: uOut = sr1 + imm5; // ADD Mode 1 3'b010: uOut = sr1 & sr2; // AND Mode 0 3'b011: uOut = sr1 & imm5; // AND Mode 1 3'b1xx: uOut = ~(sr1); // NOT endcase endmodule
Address
Address.vh
parameter AOP_SR1 = 1'b0, // address operation AOP_NPC = 1'b1, AOP_X = 1'bx;
Address.v
module Address( input aOp, // operation selector input [15:0] sr1, // value source register 1 input [15:0] nPC, // next program counter (PC), always PC+1 input [8:0] offset, // lower 9 bits from instruction register output reg [15:0] aOut ); // target program counter `include "Address.vh" wire [15:0] offset6 = ({{10{offset[5]}}, offset[5:0]}); // sign extended the 6-bit offset wire [15:0] offset9 = ({{ 7{offset[8]}}, offset[8:0]}); // sign extended the 9-bit offset always @(aOp or sr1 or nPC or offset6 or offset9) case (aOp) AOP_SR1 : aOut = sr1 + offset6; // register + offset AOP_NPC : aOut = nPC + offset9; // next PC + offset endcase endmodule
MemoryIF
MemoryIF.vh
parameter [2:0] MOP_NONE = 3'b000, // MemoryIF operation MOP_RD = 3'b100, MOP_RDI = 3'b101, MOP_WR = 3'b110, MOP_WRI = 3'b111;
MemoryIF.v
module MemoryIF( input [2:0] mOp, // memory operation selector input [15:0] sr2, // source register 2 value input [15:0] addr, // address for read or write input [15:0] eDIN, // external memory data input output reg iBR, // internal bus request output reg [15:0] iADDR, // internal memory address lines output tri [15:0] eDOUT, // internal memory data output output reg iWEA ); // internal memory write enable `include "MemoryIF.vh" reg [15:0] eDOUTr; assign eDOUT = eDOUTr; always @(mOp, sr2, addr, eDIN) case (mOp) MOP_RD : begin iBR=1; iWEA = 1'b0; iADDR = addr; eDOUTr = 16'hzzzz; end MOP_RDI : begin iBR=1; iWEA = 1'b0; iADDR = eDIN; eDOUTr = 16'hzzzz; end MOP_WR : begin iBR=1; iWEA = 1'b1; iADDR = addr; eDOUTr = sr2; end MOP_WRI : begin iBR=1; iWEA = 1'b1; iADDR = eDIN; eDOUTr = sr2; end default : begin iBR=0; iWEA = 1'bx; iADDR = 16'hxxxx; eDOUTr = 16'hzzzz; end endcase endmodule
DrMux
DrMux.vh
parameter [1:0] DRSRC_ALU = 2'b00, // destination register source selector DRSRC_MEM = 2'b01, DRSRC_ADDR = 2'b10, DRSRC_X = 2'bxx;
DrMux.v
module DrMux( input [1:0] drSrc, // multiplexor selector input [15:0] eDIN, // external memory data input input [15:0] addr, // effective memory address input [15:0] uOut, // result from ALU output reg [15:0] dr ); // data that will be stored in DR `include "DrMux.vh" always @(drSrc or uOut or eDIN or addr) case (drSrc) DRSRC_ALU : dr = uOut; DRSRC_MEM : dr = eDIN; DRSRC_ADDR : dr = addr; default : dr = 16'hxxxx; endcase endmodule:
BusDriver
BusDriver.v
module BusDriver( input br0, // input 0, bus request input [15:0] iADDR0, // input 0, internal memory address lines input iWEA0, // input 0, internal memory write enable input br1, // input 1, bus request input [15:0] iADDR1, // input 1, internal memory address lines input iWEA1, // input 1, internal memory write enable output tri [15:0] eADDR, // external memory address lines output tri eWEA ); // external memory write enable assign eWEA = br1 ? iWEA1 : br0 ? iWEA0 : 1'bz; assign eADDR = br1 ? iADDR1 : br0 ? iADDR0 : 16'hzzzz; endmodule
Functional simulation
The functionality of our microprocessor can be tested by building a test bench. The bench will supply the clock signal and reset pulse and simulate a random access memory (RAM) containing the test program. The program is written using in a assembly language and compiled using LC3Edit.
Test program
Exercises a variety of instructions:
- memory read
- alu
- memory write
- control instructions
Written in assembly language
LC3Edit compiles this into the object file:
As part of the compilation, LC3Edit also creates a .hex
file. The contents of this file can be tweaked into a .coe file to be preloaded in the test bench memory.
Memory
The random access memory (RAM) is created using Xilinx IP's Block Memory Generator 6.2. The following parameters are used:
- native i/f, single port ram, no byte write enable, minimum area algorithm, width 16, depth 4096, write first, use ENA pin, no output registers, no RSTA pin, enable all warnings. Initialize memory from the
.coe
file.
Test bench and clock/reset signals
Generate a 50 MHz symmetric clock.
Integrate the parts into a test bench using Verilog. `timescale 1ns / 1ps module SimpleLC3_SimpleLC3_sch_tb(); reg clock; // clock (generated by test fixture) reg reset; // reset (generated by test fixture) wire [15:0] eADDR; // external address (from LC3 to memory) wire [15:0] eDIN; // external data (from memory to LC3) wire [15:0] eDOUT; // external data (from LC3 to memory) wire eWEA; // external write(~read) enable (from LC3 to memory) // Instantiate the Unit Under Test SimpleLC3 UUT ( .eDOUT(eDOUT), .eWEA(eWEA), .clock(clock), .eREADY(1'b1), // ready (always 1, for now) .eDIN(eDIN), .reset(reset), .eADDR(eADDR) ); // Instantiate the Memory, created using Xilinx IP's Block Memory Generator 6.2: // Initialize from memory.coe, created from compiling memory.asm using LC3Edit. memory RAM( .clka(clock), .ena(eENA), .wea(eWEA), .addra(eADDR[11:0]), .dina(eDOUT), .douta(eDIN) ); wire eENA = |(eADDR[15:12] == 4'h3); // memory is at h3xxx initial begin clock = 0; reset = 0; #15 reset = 1; // wait for global reset to finish #22 reset = 0; end always #10 clock <= ~clock; // 20 ns clock period (50 MHz) endmodule;[/code]
Simulation results
For the functional simulation we use ISim that comes bundled with the Xilinx IDE.
The simulation needs to be ran for 1600 ns.
Waveform diagrams are shown below (click to enlarge)
Timing simulation
The free Xilinx IDE doesn't support timing simulations. Instead we will use Icarus Verilog for the synthesis and simulation, GTKWave for viewing the generated waveforms, and Emacs verilog-mode for editing. We will run them natively under Linux. For those interested, Windows binaries are available from bleyer.org.
This concludes the "Implementation of the LC-3 instruction set in Verilog".